% Ark TS Code Auditor
% James Cordy et al., Huawei Technologies

% August 2023 (Rev. Jan 2024)

% Typescript grammar
#pragma -comment
include "bom.grm"
include "js-ts.grm"

% Ark ETS grammar overrides
include "ets.grm"

% TXL string utility library
include "stringutils.rul"

% Standard ArkTS formatting - issue #72
include "tsformat.grm"

% Auditing overrides
include "tsaudit.grm"

% Line number and source file inference
include "tslinefile.rul"

% Lightweight type inference
include "tsinfer.rul"

% Default rule set and severity
include "tsrulecontrol.rul"


% Main auditing rule

function main
    % Rule controls - rules are enabled by the global list Rules, with minimum severity Severity
    construct _ [id]
        _ [ruleControls]                    % see tsrulecontrol.rul

    % Run the audit rules over the entire input and annotate the code with results
    replace [program] 
        P [program]
    by
        P % Lightweight type inference
          [lightweightTypeInference]        [clearAttributes]

          % TS auditing rules
          [auditDecimals]                   [clearAttributes]
          [auditJSONnumbers]                [clearAttributes]
          [auditCodeMotionLoops]            [clearAttributes]
          [auditPropertyDeletes]            [clearAttributes]
          [auditVar]                        [clearAttributes]
          % [auditNaN]                      [clearAttributes]   % obsolete
          [auditWith]                       [clearAttributes]
          [auditEmptyObjects]               [clearAttributes]
          [auditFunctionNestedClasses]      [clearAttributes]
          [auditFunctionNestedFunctions]    [clearAttributes]
          [auditTemplateSpaces]             [clearAttributes]
          % [auditSemicolons]               [clearAttributes]   % obsolete
          % [auditStrings]                  [clearAttributes]   % obsolete
          [auditMultipleDeclarations]       [clearAttributes]
          [auditMultipleAssignments]        [clearAttributes]
          [auditConstVariables]             [clearAttributes]
          [auditSubStatements]              [clearAttributes]
          % [auditEqualityOperands]         [clearAttributes]   % on hold for future
          [auditControlAssignments]         [clearAttributes]
          [auditReturnAwait]                [clearAttributes]
          [auditInitializationObjectProperties] [clearAttributes]
          [auditEval]                       [clearAttributes]
          [auditAny]                        [clearAttributes]
          [auditFinallyBlocks]              [clearAttributes]
          [auditDynamicFunctions]           [clearAttributes]
          [auditReturnUndefined]            [clearAttributes]
          [auditFloatEquality]              [clearAttributes]
          [auditFunctionReturnTypes]        [clearAttributes]
          [auditFunctionArgumentTypes]      [clearAttributes]
          [auditConstructorArgumentTypes]   [clearAttributes]
          [auditMethodArgumentTypes]        [clearAttributes]
          [auditEqualityOperators]          [clearAttributes]
          [auditBooleanNames]               [clearAttributes]
          [auditConstantNames]              [clearAttributes]
          [auditEnumNames]                  [clearAttributes]
          [auditVariableAndFunctionNames]   [clearAttributes]
          [auditParameterNames]             [clearAttributes]
          [auditClassEnumAndNamespaceNames] [clearAttributes]
          [auditAssignThis]                 [clearAttributes]
          [auditArgumentsParameter]         [clearAttributes]
          [auditPrototypeAssignments]       [clearAttributes]
          % [auditTypeExports]              [clearAttributes]   % temporarily disabled
          % [auditTypeImports]              [clearAttributes]   % temporarily disabled
          [auditClosures]                   [clearAttributes]
          [auditDynamicProperties]          [clearAttributes]
          [auditObjectTypeAliases]          [clearAttributes]
          [auditObjectDotNotation]          [clearAttributes]
          [auditAssignNullUndefined]        [clearAttributes]
          [auditNonNumericSubscripts]       [clearAttributes]
          [auditAwaitNonThenables]          [clearAttributes]
          [auditIndexedContainers]          [clearAttributes]

          % ArkUI auditing rules
          [auditUIIfStatements]             [clearAttributes]
          [auditUIRedundantComponents]      [clearAttributes]
          [auditUIForEach]                  [clearAttributes]
          [auditUIFlex]                     [clearAttributes]
          [auditUIAsync]                    [clearAttributes]
          [auditUISyncLoad]                 [clearAttributes]
          [auditUIRedundantStack]           [clearAttributes]
          [auditUINestLevels]               [clearAttributes]
          [auditUIListWidthHeight]          [clearAttributes]
          [auditUINestedComponents]         [clearAttributes]
          [auditUIHighFreqLogOperations]    [clearAttributes]
          [auditUIFunctionHighFreqLogOperations] [clearAttributes]
          [auditUIAnimateToSameParams]      [clearAttributes]

          % Do these last
          [auditStrict]                     [clearAttributes]
          % [auditLineLength]               % obsolete
          % [auditStringWidth]              % obsolete

          % Finalize audit comments with source file and line numbers
          [resolveLineAndFileInAuditComments]

          % Temporary fix to TXL NL comment line spacing bug
          [fixLeadingNLComment]
end function


%%%%%%%%%%%%%%%%%%%
% TS auditing rules
%%%%%%%%%%%%%%%%%%%

% Issue #1 : Do not omit 0s before and after the decimal point of a floating point number

rule auditDecimals
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-dec-num" 1]
    % Example audit analysis rule 1
    % Do not omit 0s before and after the decimal point of a floating point number
    replace $ [PrimaryExpression]
        N [number]
    % Get the text of the number
    construct Nstring [stringlit]
        _ [+ N]
    % Try to fix missing 0s
    construct NstringFixed [stringlit]
        Nstring [substleft "." "0."] 
                [substright "." ".0"]
                [subst ".e" ".0e"]
                [subst ".E" ".0E"]
    % If we fixed any ...
    deconstruct not NstringFixed
        Nstring
    % ... then replace with the fixed version and message
    % Note: we cannot use [number] for the fixed version, since TXL removes redundant 0s, as is the printf() standard
    construct NfixedId [id]
        _ [+ NstringFixed] 
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not omit 0s before and after the decimal point (fixed) : hp-specs-dec-num : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment NfixedId
end rule

% Issue #5 : Do not use float number as the value for textsize and coordinates 

rule auditJSONnumbers
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-no-float-number" 1]
    % For each top-level JSON object literal
    skipping [SourceElement]
    replace $ [SourceElement*]
        Stmt [STMT] JSON [ObjectLiteral] Semi [SEMI]
        MoreElements [SourceElement*]
    % Round the float properties in it
    construct NewJSON [ObjectLiteral]
        JSON [roundProperties]
    deconstruct not NewJSON
        JSON
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use float number as the value for textsize and coordinates (fixed) : hp-performance-no-float-number : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt NewJSON Semi 'audited
        MoreElements
end rule

rule roundProperties
    % Round all of the property values in the object literal
    replace $ [PropertyDefinition]
        PropertyName [PropertyName] Nullable [Nullable?] ': Initializer [PropertyInitializer]
    % Only do direct ones
    deconstruct not * [PropertyDefinition] Initializer
        _ [PropertyDefinition]
    % Are there any to round?
    construct RoundedInitializer [PropertyInitializer]
        Initializer [roundFloatValues]
    deconstruct not RoundedInitializer
        Initializer
    % Then round them
    by
        PropertyName Nullable ': RoundedInitializer
end rule

rule roundFloatValues
    skipping [MemberSelector]
    replace $ [number]
        N [number]
    % Is it a floating point value?
    construct Nstring [stringlit]
        _ [+ N]
    where
        Nstring [grep "."]
    % The warn and round it
    construct Nrounded [number]
        N [roundFloat]
    by
        Nrounded
end rule

function roundFloat
    % Work around bug in TXL [round] function due to gcc change in meaning of (int) x
    replace [number]
        N [number]
    by
        N [+ 0.5] [trunc]
end function

% Issue #4 : Do not access a const property in a heavy loop

rule auditCodeMotionLoops
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-no-const-prop-in-heavy-loop" 1]
    % Move constant expressions outside of for loops
    replace $ [SourceElement*]
        ForStatement [SourceElement]
        MoreStatements [SourceElement*]
    deconstruct ForStatement
        Stmt [STMT] ForHeader [ForHeader] ForBody [Statement] Semi [SEMI]
    % Get the index variables
    construct ForIndexes [BindingIdentifier*]
        _ [^ ForStatement]
    construct ForAssignments [LeftHandSideExpression*]
        _ [^ ForHeader]
    construct ForIndexIds [Identifier*]
        _ [^ ForIndexes] [^ ForAssignments]
    % Get the independent expressions
    export DirectExpressions [RelationalExpression*]
        _ % empty
    where
        ForBody [hasDirectExpressions]
    import DirectExpressions
    construct Expressions [RelationalExpression*]
        DirectExpressions 
            [onlyInterestingExpressions] 
            [onlyArithmeticExpressions] 
            [onlyIndependentExpressions ForIndexIds] 
            [onlyWholeExpressions]
    % Make const declarations for them
    construct ConstDeclarations [SourceElement*]
        _ [makeConstDeclaration each Expressions]
    % Convert the for statement
    construct NewForStatement [SourceElement]
        Stmt ForHeader ForBody [replaceConstExpressions each ConstDeclarations] Semi 'audited
    by
        ConstDeclarations [. NewForStatement] [. MoreStatements]
end rule

rule hasDirectExpressions
    skipping [ForStatement] [FunctionBody]
    match $ [RelationalExpression]
        DirectExpression [RelationalExpression]
    import DirectExpressions [RelationalExpression*]
    export DirectExpressions
        DirectExpressions [. DirectExpression]
end rule

function makeConstDeclaration Expression [RelationalExpression]
    % Make a uniquely named const declaration for each constant expression
    replace * [SourceElement*]
        % end of list
    construct Tid [id]
        'T
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not access a const property in a heavy loop (fixed) : hp-performance-no-const-prop-in-heavy-loop : 1 : LINENUMBER : FILEPATH */
    construct ConstDeclaration [VariableDeclaration]
        'const Tid [!] '= Expression ';
    by
        AuditComment ConstDeclaration
end function

function replaceConstExpressions ConstDeclaration [SourceElement]
    % If we have a const declaration for the expression
    deconstruct * [VariableDeclaration] ConstDeclaration
        _ [STMT] 'const TempId [Identifier] '= Expression [RelationalExpression] _ [SEMI]
    construct TempIdExpression [RelationalExpression]
        TempId
    % The replace every copy of it in the loop with the const id
    replace [Statement]
        ForBody [Statement]
    by
        ForBody [$ Expression TempIdExpression]
end function

rule onlyInterestingExpressions
    % Most member expressions are too simple to be interesting
    replace [RelationalExpression*]
        UnaryExpression [UnaryExpression]
        MoreExpressions [RelationalExpression*]
    deconstruct not * [MemberSelector] UnaryExpression
        _ [SubscriptOrPropertySelector]
    deconstruct not * [Addition] UnaryExpression
        _ [Addition]
    deconstruct not * [Multiplication] UnaryExpression
        _ [Multiplication]
    by
        MoreExpressions
end rule

rule onlyArithmeticExpressions
    % Relational expressions are not appropriate for code motion
    replace [RelationalExpression*]
        RelationalExpression [RelationalExpression]
        MoreExpressions [RelationalExpression*]
    deconstruct RelationalExpression
        _ [RelationalExpression] _ [RelationalOp] _ [ShiftExpression]
    by
        MoreExpressions
end rule

function onlyWholeExpressions
    replace [RelationalExpression*]
        Expressions [RelationalExpression*]
    by
        Expressions [removeSubexpressions each Expressions]
end function

rule removeSubexpressions RelationalExpression [RelationalExpression]
    % Some extracted expressions may contan others
    replace [RelationalExpression*]
        SubExpression [RelationalExpression]
        MoreExpressions [RelationalExpression*]
    deconstruct not RelationalExpression
        SubExpression
    deconstruct * [RelationalExpression] RelationalExpression
        SubExpression
    by
        MoreExpressions
end rule

rule onlyIndependentExpressions ForIndexIds [Identifier*]
    % An expression is independent of the loop if it does not refer to any for indexes
    replace [RelationalExpression*]
        Expression [RelationalExpression]
        MoreExpressions [RelationalExpression*]
    where
        Expression [references each ForIndexIds]
    by
        MoreExpressions
end rule

function references Id [Identifier]
    % Does the expression reference the identifier?
    match * [IdentifierReference]
        Id
end function

% Issue #9 : Do not delete any properties of an object

function auditPropertyDeletes
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-no-delete-objs-props" 1]
    replace [program]
        P [program]
    by
        P [propertyDeleteStatements]
          [undefinedPropertyTypes]
          [variableDeleteStatements]
          [otherDeleteStatements]
end function

rule propertyDeleteStatements
    % Convert any property deletions into null assignments
    export DeletedPropertyIds [Identifier*]
        _ % none
    replace $ [SourceElement*]
        Stmt [STMT] 'delete ObjectProperty [MemberExpression] Semi [SEMI]
        MoreStatements [SourceElement*]
    skipping [MemberSelector]
    deconstruct * [MemberSelector*] ObjectProperty
        '. PropertyId [Identifier]
    import DeletedPropertyIds
    export DeletedPropertyIds
        PropertyId
        DeletedPropertyIds
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not delete properties of an object (fixed) : hp-performance-no-delete-objs-props : 1 : LINENUMBER : FILEPATH */
    by 
        AuditComment
        Stmt ObjectProperty '= 'null Semi
        MoreStatements
end rule

rule undefinedPropertyTypes
    % Convert any possibly deleted property types into null instead
    import DeletedPropertyIds [Identifier*]
    replace $ [ClassElement*]
        Stmt [STMT] Modifiers [AccessibilityModifier*] PropertyId [Identifier] Nullable [Nullable ?] Type [TypeAnnotation?] 
            Initializer [Initializer?] Semi [SEMI]
        MoreElements [ClassElement*]
    deconstruct * [Identifier] DeletedPropertyIds
        PropertyId
    deconstruct * [PrimaryType] Type
        'undefined
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not delete properties of an object (fixed) : hp-performance-no-delete-objs-props : 1 : LINENUMBER : FILEPATH */
    by 
        AuditComment
        Stmt Modifiers PropertyId Nullable Type [$ 'undefined 'null] Initializer Semi
        MoreElements
end rule

rule variableDeleteStatements
    % Convert variable deletions into null assignments
    export DeletedVariableIds [Identifier*]
        _ % none
    replace $ [SourceElement*]
        Stmt [STMT] 'delete VariableId [Identifier] Semi [SEMI]
        MoreStatements [SourceElement*]
    import DeletedVariableIds
    export DeletedVariableIds
        VariableId
        DeletedVariableIds
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not delete variables (fixed) : hp-performance-no-delete-objs-props : 1 : LINENUMBER : FILEPATH */
    by 
        AuditComment
        Stmt VariableId '= 'null Semi
        MoreStatements
end rule

rule undefinedVariableTypes
    % Convert any possibly deleted variables into null instead
    import DeletedVariableIds [Identifier*]
    replace $ [SourceElement*]
        Stmt [STMT] VarLetOrConst [VarLetOrConst] PropertyId [Identifier] Nullable [Nullable ?] Type [TypeAnnotation?] 
            Initializer [Initializer?] Semi [SEMI]
        MoreElements [SourceElement*]
    deconstruct * [Identifier] DeletedVariableIds
        PropertyId
    deconstruct * [PrimaryType] Type
        'undefined
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not delete properties of an object (fixed) : hp-performance-no-delete-objs-props : 1 : LINENUMBER : FILEPATH */
    by 
        AuditComment
        Stmt VarLetOrConst PropertyId Nullable Type [$ 'undefined 'null] Initializer Semi
        MoreElements
end rule

rule otherDeleteStatements
    % Flag other deletes - can't necessarily find their declarations
    replace $ [SourceElement*]
        Stmt [STMT] 'delete MemberExpression [MemberExpression] Semi [SEMI]
        MoreStatements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not delete properties of an object (fixed) : hp-performance-no-delete-objs-props : 1 : LINENUMBER : FILEPATH */
    by 
        AuditComment
        Stmt MemberExpression '= 'null Semi
        MoreStatements
end rule

% Issue #15 : Do not use indexed containers as maps

function auditIndexedContainers
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-proper-data-structures" 1]
    replace [program]
        P [program]
    by
        P [auditIndexedContainerDeclarations]
          [auditIndexedContainerUses]
end function

rule auditIndexedContainerDeclarations
    skipping [AuditedIndexSignature]
    replace $ [IndexSignature]
        IndexSignature [IndexSignature]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use indexed containers as maps : hp-performance-proper-data-structures : 1 : LINENUMBER : FILEPATH */
    by 
        AuditComment IndexSignature
end rule

rule auditIndexedContainerUses
    replace $ [MemberExpression]
        ObjectId [Identifier] '[ '[: 'string '] UntypedAssignmentExpression [UntypedAssignmentExpression] ']  MoreMemberSelectors [MemberSelector*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use indexed containers as maps : hp-performance-proper-data-structures : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment ObjectId '[ '[: 'string ']  UntypedAssignmentExpression '] MoreMemberSelectors
end rule

% Issue #35 : Use const or let when declaring variables

rule auditVar
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-use-const-or-let-for-vars" 1]
    replace $ [SourceElement*]
        Stmt [STMT] 'var VariableBindingList [VariableBindingList] Semi [SEMI]
        MoreElements [SourceElement*]
    construct ConstOrLet [VarLetOrConst]
        'const
    construct AuditComment [AuditComment]
        '/* HPAudit: Use const or let when declaring variables (fixed) : hp-specs-use-const-or-let-for-vars : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt ConstOrLet [letIfAssigned VariableBindingList MoreElements] VariableBindingList Semi
        MoreElements
end rule

rule letIfAssigned VariableBindingList [VariableBindingList] Scope [SourceElement*]
    construct BindingIdentifiers [BindingIdentifier*]
        _ [^ VariableBindingList]
    construct Identifiers [Identifier*]
        _ [^ BindingIdentifiers]
    where
        Scope [assigns each Identifiers]
              [updates each Identifiers]
    replace [VarLetOrConst]
        'const
    by
        'let
end rule

function assigns Identifier [Identifier]
    match * [LeftHandSideExpression]
        LeftHandSideExpression [LeftHandSideExpression]
    deconstruct * [Identifier] LeftHandSideExpression
        Identifier
end function

function updates Identifier [Identifier]
    match * [UpdateExpression]
        UpdateExpression [UpdateExpression]
    skipping [MemberExpression]
    deconstruct * [MemberExpression] UpdateExpression
        Identifier
    deconstruct * [UpdateOp] UpdateExpression
        _ [UpdateOp]
end function

% Issue #37 : Use isNaN() to determine if a variable is NaN

% obsolete
%(
rule auditNaN
    replace $ [SourceElement*]
        IsNaNTest [SourceElement]
        MoreElements [SourceElement*]
    skipping [SourceElements]
    deconstruct * [EqualityExpression] IsNaNTest
        RelationalExpression [RelationalExpression] EqualityOp [EqualityOp] 'NaN
    construct EqualsNaNExpression [EqualityExpression]
        RelationalExpression EqualityOp 'NaN
    construct IsNaNExpression [EqualityExpression]
        'isNan '( RelationalExpression ')
    construct NegatedIsNaNExpression [EqualityExpression]
        IsNaNExpression [negateIfNotEqual EqualityOp]
    construct AuditComment [AuditComment]
        '/* HPAudit: Use isNaN() to determine if a variable is NaN (fixed) */ */
    by
        AuditComment
        IsNaNTest [$ EqualsNaNExpression NegatedIsNaNExpression]
        MoreElements
end rule

function negateIfNotEqual EqualityOp [EqualityOp]
    construct NotEqualOps [EqualityOp*]
        '!= '!==
    deconstruct * [EqualityOp] NotEqualOps
        EqualityOp
    replace [EqualityExpression]
        IsNaNExpression [MemberExpression]
    by
        '! IsNaNExpression
end function
)%

% Issue #32 : Do not use eval()

rule auditEval
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-eval" 1]
    skipping [AuditedUnaryExpression]
    replace $ [UnaryExpression]
        EvalUse [UnaryExpression]
    skipping [SourceElements]
    deconstruct * [MemberExpression] EvalUse 
        'eval _ [Arguments] _ [MemberSelector*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use eval() : hp-specs-no-eval : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment EvalUse
end rule

% Issue #33 : Do not use with()

rule auditWith
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-with" 1]
    replace $ [SourceElement*]
        WithStatement [WithStatement]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use with() : hp-specs-no-with : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        WithStatement 'audited
        MoreElements
end rule

% Issue #17 : Do not create empty objects

rule auditEmptyObjects
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-objs-using-literals" 1]
    construct ObjectOrArrays [Identifier*]
        'Object 'Array
    replace $ [SourceElement*]
        NewObjectOrArray [Declaration]
        MoreElements [SourceElement*]
    skipping [SourceElements]
    deconstruct * [MemberExpression] NewObjectOrArray
        'new ObjectOrArray [Identifier] _ [Arguments]
    deconstruct * [Identifier] ObjectOrArrays
        ObjectOrArray
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not create empty objects, use literals instead : hp-performance-objs-using-literals : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        NewObjectOrArray 'audited
        MoreElements
end rule

% Issue #15 : Declare unchanged variables as const

rule auditConstVariables
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-unchanged-const-vars" 1]
    replace $ [SourceElement*]
        Stmt [STMT] 'let VariableBindingList [VariableBindingList] Semi [SEMI] 
        MoreElements [SourceElement*]
    construct Const [VarLetOrConst]
        'const
    construct ConstOrLet [VarLetOrConst]
        Const [letIfAssigned VariableBindingList MoreElements]
    deconstruct not ConstOrLet
        'let
    construct AuditComment [AuditComment]
        '/* HPAudit: Declare unchanged variables as const (fixed) : hp-performance-unchanged-const-vars : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt ConstOrLet VariableBindingList Semi
        MoreElements
end rule

% Issue #19 : Do not dynamically declare functions and classes

function auditFunctionNestedClasses
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-no-dynamic-cls-func" 1]
    replace [program]
        P [program]
    by
        P [auditIndependentFunctionNestedClasses]
          [auditDependentFunctionNestedClasses]
end function

rule auditIndependentFunctionNestedClasses
    % Move independent nested classes outside of the function
    replace $ [SourceElement*]
        FunctionDeclaration [FunctionDeclaration]
        MoreElements [SourceElement*]
    % Get a locally declared class
    deconstruct * [SourceElement] FunctionDeclaration
        ClassDeclaration [ClassDeclaration]
    % Remove it from the function
    construct NewFunctionDeclaration [FunctionDeclaration]
        FunctionDeclaration [removeClass ClassDeclaration]
    % Get local bindings
    export LocalIdentifiers [Identifier*]
        _ % none
    where
        NewFunctionDeclaration [getParameters] 
                               [getLocalVariables]
    import LocalIdentifiers
    % Is the class independent of the function?
    where
        ClassDeclaration [isIndependentClass LocalIdentifiers]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not dynamically declare functions and classes (fixed) : hp-performance-no-dynamic-cls-func : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ClassDeclaration
        NewFunctionDeclaration
        MoreElements
end rule

function getParameters
    match * [Parameters]
        Parameters [Parameters]
    construct ParameterBindingIds [BindingIdentifier*]
        _ [^ Parameters]
    construct ParameterIds [Identifier*]
        _ [^ ParameterBindingIds]
    import LocalIdentifiers [Identifier*]
    export LocalIdentifiers
        LocalIdentifiers [. ParameterIds]
end function

function getLocalVariables
    match * [FunctionBlock]
        FunctionBlock [FunctionBlock]
    where
        FunctionBlock [getDeclaredVariables]
end function

rule getDeclaredVariables
    skipping [ClassBody] [OptionalFunctionBody] 
    match $ [BindingIdentifier]
        Identifier [Identifier]
    import LocalIdentifiers [Identifier*]
    export LocalIdentifiers
        LocalIdentifiers [. Identifier]
end rule

function isIndependentClass LocalIdentifiers [Identifier*]
    match [ClassDeclaration]
        ClassDeclaration [ClassDeclaration]
    where not
        ClassDeclaration [references each LocalIdentifiers]
end function

function removeClass ClassDeclaration [ClassDeclaration]
    replace * [SourceElement]
        ClassDeclaration
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not dynamically declare functions and classes (fixed) : hp-performance-no-dynamic-cls-func : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
end function

rule auditDependentFunctionNestedClasses
    % Report dependent classes inside a function
    replace $ [FunctionDeclaration]
        FunctionDeclaration [FunctionDeclaration]
    by
        FunctionDeclaration [auditDependentNestedClasses]
                            [auditExpressionNestedClasses]
end rule

rule auditDependentNestedClasses
    % We already handled independent classes, so any remaining are dependent
    replace $ [SourceElement*]
        ClassDeclaration [ClassDeclaration]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not dynamically declare functions and classes : hp-performance-no-dynamic-cls-func : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ClassDeclaration 'audited
        MoreElements
end rule

rule auditExpressionNestedClasses
    skipping [AuditedPrimaryExpression]
    replace $ [PrimaryExpression]
        ClassExpression [ClassExpression]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not dynamically declare functions and classes : hp-performance-no-dynamic-cls-func : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ClassExpression
end rule

rule auditFunctionNestedFunctions
    % Change nested functions into lambda functions
    replace $ [FunctionDeclaration]
        FunctionDeclaration [FunctionDeclaration]
    by
        FunctionDeclaration [auditNestedFunctions]
                            [auditExpressionNestedFunctions]
end rule

rule auditNestedFunctions
    replace $ [SourceElement*]
        Stmt [STMT] Async ['async ?] 'function FunctionId [BindingIdentifier] CallSignature [CallSignature] FunctionBlock [FunctionBlock] Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not dynamically declare functions and classes (fixed) : hp-performance-no-dynamic-cls-func : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'const FunctionId '= CallSignature '=> FunctionBlock Semi 'audited
        MoreElements
end rule

rule auditExpressionNestedFunctions
    skipping [AuditedPrimaryExpression]
    replace $ [PrimaryExpression]
        FunctionExpression [FunctionExpression]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not dynamically declare functions and classes : hp-performance-no-dynamic-cls-func : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        FunctionExpression
end rule


% Normalize templates

rule auditTemplateSpaces
    replace $ [Template]
        Template [SubstitutionTemplate]
    construct TemplateString [stringlit]
        _ [+ Template]
          [substglobal "${ " "${"]
          [substglobal " }" "}"]
    construct NewTemplate [SubstitutionTemplate]
        _ [+ TemplateString]
    by
        NewTemplate
end rule

% Normalize semicolons

% obsolete
%(
function auditSemicolons
    % While not strictly necessary, normalizing the use of semicolons to the TS standard
    % both makes output code more readable and in some cases is required
    replace [program]
        P [program]
    by
        P [normalizeSourceElementSemicolons]
          [normalizeClassElementSemicolons]
          [normalizeBlockEnds]
end function

rule normalizeSourceElementSemicolons
    % If a global or local statement or declaration is issing a semicolon, add one
    replace $ [SourceElement]
        SourceElement [SourceElement]
    deconstruct not * [SourceElements] SourceElement
        _ [SourceElements]
    by
        SourceElement [normalizeEOS]
end rule

rule normalizeClassElementSemicolons
    % Similarly for class properties and other elements
    replace $ [ClassElement]
        ClassElement [ClassElement]
    deconstruct not * [SourceElements] ClassElement
        _ [SourceElements]
    by
        ClassElement [normalizeEOS]
end rule

function normalizeEOS
    % If an end-of-statement does not have a semicolon, add one to it
    skipping [EmptyStatement]
    replace * [EOS]
        NoSemicolon [EOS]
    deconstruct NoSemicolon
        SrcLineNumber [attr srclinenumber] Comments [comment_or_NL]
    by
        SrcLineNumber '; Comments
end function

rule normalizeBlockEnds
    % If a block of statements or declarations { ... } has a trailing semicolon,
    % it's considered better style to remove it
    skipping [ForHeader]
    replace $ [END]
        '; CNL [comment_or_NL] _ [optional_semi]
    by
        CNL
end rule
)%

% Use single quotes for strings

% obsolete
%(
rule auditStrings
    replace [StringLiteral]
        Stringlit [stringlit]
    construct Charlit [charlit]
        _ [+ Stringlit]
    construct AuditComment [AuditComment]
        '/* HPAudit: Use single quotes for strings (fixed) */ */
    by
        AuditComment Charlit
end rule
)%

% Issue #71 : Do not define multiple variables on a single line

function auditMultipleDeclarations
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-multi-vars-single-line" 1]
    replace [program]
        P [program]
    by
        P [auditMultipleDeclarationsSemi]
          [auditMultipleMemberDeclarationsSemi]
          [auditMultipleStructDeclarationsSemi]
          [auditMultipleDeclarationsComma]      % must be last to avoid multiple messages!
end function

rule auditMultipleDeclarationsComma
    % Separate multiple declarations into separate single declarations,
    replace [SourceElement*]
        Stmt [STMT] VarLetOrConst [VarLetOrConst] Binding1 [VariableBinding] , Binding2 [VariableBinding] , MoreBindings [list VariableBinding] Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not define multiple variables on a single line (fixed) : hp-specs-no-multi-vars-single-line : 1 : LINENUMBER : FILEPATH */
    % Separate each declared item from the rest in the list
    by
        AuditComment
        Stmt VarLetOrConst Binding1 ';
        VarLetOrConst Binding2 , MoreBindings Semi
        MoreElements
end rule

rule auditMultipleDeclarationsSemi
    replace $ [SourceElement*]
        Declaration1 [Declaration]
        Declaration2 [Declaration]
        MoreElements [SourceElement*]
    deconstruct * [srclinenumber] Declaration1
        LineNumber [srclinenumber]
    deconstruct * [srclinenumber] Declaration2
        LineNumber
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not define multiple variables on a single line (fixed) : hp-specs-no-multi-vars-single-line : 1 : LINENUMBER : FILEPATH */
    % Items are separated by our output format
    by
        AuditComment
        Declaration1 'audited
        Declaration2
        MoreElements
end rule

rule auditMultipleMemberDeclarationsSemi
    replace $ [ClassElement*]
        Declaration1 [PropertyMemberDeclaration]
        Declaration2 [PropertyMemberDeclaration]
        MoreElements [ClassElement*]
    % If they are both on the same line
    deconstruct * [srclinenumber] Declaration1
        LineNumber [srclinenumber]
    deconstruct * [srclinenumber] Declaration2
        LineNumber
    % Then warnn about it
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not define multiple variables on a single line (fixed) : hp-specs-no-multi-vars-single-line : 1 : LINENUMBER : FILEPATH */
    % Items are separated by our output format
    by
        AuditComment
        Declaration1 'audited
        Declaration2
        MoreElements
end rule

rule auditMultipleStructDeclarationsSemi
    replace $ [StructElement*]
        Declaration1 [MemberVariableDeclaration]        %%% What about struct variable declarations?
        Declaration2 [MemberVariableDeclaration]
        MoreElements [StructElement*]
    % If they are both on the same line
    deconstruct * [srclinenumber] Declaration1
        LineNumber [srclinenumber]
    deconstruct * [srclinenumber] Declaration2
        LineNumber
    % Then warnn about it
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not define multiple variables on a single line (fixed) : hp-specs-no-multi-vars-single-line : 1 : LINENUMBER : FILEPATH */
    % Items are separated by our output format
    by
        AuditComment
        Declaration1 'audited
        Declaration2
        MoreElements
end rule

function auditMultipleAssignments
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-multi-vars-single-line" 1]
    replace [program]
        P [program]
    by
        P [auditMultipleAssignmentsComma]
          [auditMultipleAssignmentsSemi]
end function

rule auditMultipleAssignmentsComma
    % Similarly, separate assignment lists into separate assignments
    replace [SourceElement*]
        Stmt [STMT] AssignmentExpression1 [AssignmentExpression] ', AssignmentExpression2 [AssignmentExpression] ',
            MoreExpressions [list AssignmentExpression] Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign multiple variables on a single line (fixed) : hp-specs-no-multi-vars-single-line : 1 : LINENUMBER : FILEPATH */
    % Separate each assignment from the rest in the list
    by
        AuditComment
        Stmt AssignmentExpression1 ';
        AssignmentExpression2 ', MoreExpressions Semi
        MoreElements
end rule

rule auditMultipleAssignmentsSemi
    replace $ [SourceElement*]
        Stmt1 [STMT] Assignment1 [AssignmentExpression] Semi1 [SEMI]
        Stmt2 [STMT] Assignment2 [AssignmentExpression] Semi2 [SEMI]
        MoreElements [SourceElement*]
    % If they are both assignments
    deconstruct Assignment1
        _ [attr ExpressionType] _ [Assignment]
    deconstruct Assignment2
        _ [attr ExpressionType] _ [Assignment]
    % On the same line
    deconstruct * [srclinenumber] Stmt1
        LineNumber [srclinenumber]
    deconstruct * [srclinenumber] Stmt2
        LineNumber
    % Then warn about it
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign multiple variables on a single line (fixed) : hp-specs-no-multi-vars-single-line : 1 : LINENUMBER : FILEPATH */
    % Items are separated by our output format
    by
        AuditComment
        Stmt1 Assignment1 Semi1 'audited
        Stmt2 Assignment2 Semi2 
        MoreElements 
end rule

% Issue #69 : Must use braces for conditional statements and loops

rule auditSubStatements
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-use-braces-stmts-loops" 1]
    replace $ [SubStatement]
        Statement [Statement]
    deconstruct not Statement
        _ [BlockStatement]
    deconstruct not Statement
        _ [IfStatement]
    construct NL [comment]
        _ [+ ""]
    construct AuditComment [AuditComment]
        '/* HPAudit: Always use braces for conditional statements and loops (fixed) : hp-specs-use-braces-stmts-loops : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        '{ Statement '} NL
end rule

% Issue #31 : Always use strict

function auditStrict
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-use-strict-mode" 1]
    % Do not add use strict to ets programs
    import TXLinput [stringlit]
    where not
        TXLinput [egrep ".ets$"]
    % Top level only
    skipping [SourceElements]
    replace * [SourceElements]
        SourceElements[SourceElement*]
    deconstruct not * [StringLiteral] SourceElements
        '"use strict"
    construct AuditComment [AuditComment]
        '/* HPAudit: Always use strict mode (fixed) : hp-specs-use-strict-mode : 1 : 0 : FILEPATH */
    by
        AuditComment
        '"use strict" ;
        SourceElements
end function

% Issue #70 : Limit line length to 120 characters

% obsolete
%(
function auditLineLength
    replace [program]
        P [program]
    by
        P [pragma "--newline -w 120"]   % Enable line wrapping in output  
end function
)%

% Limit string literals to 80 characters

% obsolete
%(
function auditStringWidth
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-wrap-long-lines" 1]
    replace [program]
        P [program]
    by
        P [auditWideStrings]
          [auditWideChars]
          [auditWideTemplates]
end function

rule auditWideStrings
    skipping [AuditedPrimaryExpression]
    replace $ [PrimaryExpression]
        S [stringlit]
    construct Slength [number]
        _ [# S]
    where
        Slength [> 80]
    construct AuditComment [AuditComment]
        '/* HPAudit: Consider splitting very long strings : hp-specs-wrap-long-lines : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment S 
end rule

rule auditWideChars
    skipping [AuditedPrimaryExpression]
    replace $ [PrimaryExpression]
        S [charlit]
    construct Slength [number]
        _ [# S]
    where
        Slength [> 80]
    construct AuditComment [AuditComment]
        '/* HPAudit: Consider splitting very long strings : hp-specs-wrap-long-lines : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment S 
end rule

rule auditWideTemplates
    skipping [AuditedPrimaryExpression]
    replace $ [PrimaryExpression]
        S [SubstitutionTemplate]
    construct Slength [number]
        _ [# S]
    where
        Slength [> 82]
    construct AuditComment [AuditComment]
        '/* HPAudit: Consider splitting very long strings : hp-specs-wrap-long-lines : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment S 
end rule
)%

% Issue #59 : Avoid using any

rule auditAny
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-any" 1]
    % Find all any types
    skipping [AuditedType] [ExpressionType]
    replace $ [Type]
        'any
    construct AuditComment [AuditComment]
        '/* HPAudit: Avoid using 'any' : hp-specs-no-any : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment 
        'any
end rule

% Issue #45, Case 2 : Confusing equality operator

function auditEqualityOperands
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-vars-control-condition-expns" 1]
    replace [program]
        P [program]
    by
        P [auditEqualityOperandsLeft]
          [auditEqualityOperandsRight]
end function

rule auditEqualityOperandsLeft
    replace $ [Equality]
        Expression1 [EqualityExpression] EqualityOp [EqualityOp] Expression2 [RelationalExpression]
    deconstruct not Expression1
        _ [UnaryExpression]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly group equality operands (fixed) : hp-specs-no-vars-control-condition-expns : 1 : LINENUMBER : FILEPATH */
    by
        '( Expression1 ') AuditComment EqualityOp Expression2
end rule

rule auditEqualityOperandsRight
    replace $ [Equality]
        Expression1 [EqualityExpression] EqualityOp [EqualityOp] Expression2 [RelationalExpression]
    deconstruct not Expression2
        _ [UnaryExpression]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly group equality operands (fixed) : hp-specs-no-vars-control-condition-expns : 1 : LINENUMBER : FILEPATH */
    by
        Expression1 AuditComment EqualityOp '( Expression2 ')
end rule

% Issue #45, #386 : Assignments in control expressions

function auditControlAssignments
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-vars-control-condition-expns" 1]
    replace [program]
        P [program]
    by
        P [auditIfControlAssignments]
          [auditWhileControlAssignments]
          [auditDoControlAssignments]
          [auditForControlAssignments]
          [auditConditionalExpressionControlAssignments]
end function

rule auditIfControlAssignments
    replace [SourceElement*]
        Stmt [STMT] 'if '( Assignment [AssignmentExpression] ') Comments [CommentStatement*] SubStatement [SubStatement]  
            ElseStatement [ElseStatement?] Semi [SEMI]
        MoreSourceElements [SourceElement*]
    deconstruct Assignment
        LeftHandSideType [attr ExpressionType] LeftHandSide [MemberExpression] AssignmentOp [AssignmentOp] 
            RightHandSide [AssignmentExpression]
    construct AssignmentStatement [SourceElement]
        Assignment ';
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign variables in control conditions (fixed) : hp-specs-no-vars-control-condition-expns : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        AssignmentStatement
        Stmt 'if '( LeftHandSide ') Comments SubStatement ElseStatement Semi
        MoreSourceElements
end rule

rule auditWhileControlAssignments
    replace [SourceElement*]
        Stmt [STMT] 'while '( Assignment [AssignmentExpression] ') Block [BLOCK] '{ Statements [SourceElement*] '} End [END] 
        Semi [SEMI]
        MoreSourceElements [SourceElement*]
    deconstruct Assignment
        LeftHandSideType [attr ExpressionType] LeftHandSide [MemberExpression] AssignmentOp [AssignmentOp] 
            RightHandSide [AssignmentExpression]
    construct AssignmentStatement [SourceElement]
        Assignment ';
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign variables in control conditions (fixed) : hp-specs-no-vars-control-condition-expns : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        AssignmentStatement
        Stmt 'while '( LeftHandSide ') Block '{ Statements [. AssignmentStatement] '} End Semi
        MoreSourceElements
end rule

rule auditDoControlAssignments
    replace [SourceElement*]
        Stmt [STMT] 'do Block [BLOCK] '{ Statements [SourceElement*] '} End [END] 'while '( Assignment [AssignmentExpression] ') Semi [SEMI]
        MoreSourceElements [SourceElement*]
    deconstruct Assignment
        LeftHandSideType [attr ExpressionType] LeftHandSide [MemberExpression] AssignmentOp [AssignmentOp] 
            RightHandSide [AssignmentExpression]
    % Preserve line number
    deconstruct * [srclinenumber] End
        SrcLineNumber [srclinenumber]
    construct AssignmentStatement [SourceElement]
        Assignment SrcLineNumber ';
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign variables in control conditions (fixed) : hp-specs-no-vars-control-condition-expns : 1 : LINENUMBER : FILEPATH */
    construct AuditCommentStatement [SourceElement]
        AuditComment
    by
        Stmt 'do Block '{ Statements [. AuditCommentStatement] [. AssignmentStatement] '} End 'while '( LeftHandSide ') Semi
        MoreSourceElements
end rule

rule auditForControlAssignments
    replace [SourceElement*]
        Stmt [STMT] 'for Await ['await ?] '( ForInitializer [ForInitializer] '; Assignment [AssignmentExpression] ForIterators [ForIterator*] ') 
            Block [BLOCK] '{ Statements [SourceElement*] '} End [END] Semi [SEMI]
        MoreSourceElements [SourceElement*]
    deconstruct Assignment
        LeftHandSideType [attr ExpressionType] LeftHandSide [MemberExpression] AssignmentOp [AssignmentOp] 
            RightHandSide [AssignmentExpression]
    construct AssignmentStatement [SourceElement]
        Assignment ';
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign variables in control conditions (fixed) : hp-specs-no-vars-control-condition-expns : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'for Await '( ForInitializer '; RightHandSide ForIterators ') 
            Block '{ AssignmentStatement Statements '} End Semi
        MoreSourceElements
end rule

rule auditConditionalExpressionControlAssignments
    replace [SourceElement*]
        SourceElement [SourceElement]
        MoreSourceElements [SourceElement*]
    skipping [SourceElements]
    deconstruct * [ConditionalExpression] SourceElement
        ConditionalExpression [ConditionalExpression] '? AssignmentExpression1 [AssignmentExpression] ': AssignmentExpression2 [AssignmentExpression]
    deconstruct * [UntypedAssignmentExpression] ConditionalExpression
        Assignment [Assignment]
    deconstruct Assignment
        LeftHandSide [MemberExpression] AssignmentOp [AssignmentOp] RightHandSide [AssignmentExpression]
    construct AssignmentStatement [SourceElement]
        Assignment ';
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign variables in control conditions (fixed) : hp-specs-no-vars-control-condition-expns : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Assignment
        SourceElement [convertAssignment LeftHandSide RightHandSide]
        MoreSourceElements
end rule

function convertAssignment LeftHandSide [MemberExpression] RightHandSide [AssignmentExpression]
    replace * [AssignmentExpression]
        LeftHandSideType [attr ExpressionType] LeftHandSide AssignmentOp [AssignmentOp] RightHandSide
    by
        LeftHandSide
end function

% Issue #18, Initialize object properties 

% Audit Initialization of object Properties
rule auditInitializationObjectProperties
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-initialize-obj-props" 1]
    replace $ [ClassDeclaration]
        Stmt [STMT]
        ClassHeader[ClassHeader] Block [BLOCK]'{ 
            ClassBody [ClassBody] 
        '} End [END]
        Semi [SEMI]
    by
        Stmt
        ClassHeader Block '{ 
            ClassBody   [memberVariableInitialization ClassBody] 
                        [memberVariableInitializationWarnOnlyWithUnknownType ClassBody] 
        '} End 
        Semi
end rule

% Initialize uninitialized object properties when we have the type
define BuiltinTypeValueMapping  
    [Type] ': [MemberExpression] ';
end define

rule memberVariableInitialization ClassBody[ClassBody] 
    % Table of initial values for non-initialized object properties
    construct builtinTypeValueMappings [BuiltinTypeValueMapping*]
        'string                 ': ''' '; 
        'number                 ': '0 ';
        'bigint                 ': '0n ';
        'boolean                ': 'false ';
        'Number                 ': 'new 'Number(0) ';
        'String                 ': 'new 'String(''') ';
        'Boolean                ': 'new 'Boolean(false) ';
        'Object                 ': 'new 'Object() ';
        'Function               ': 'function '() '{} ';
        'symbol                 ': 'Symbol() ';
        'URL                    ': 'new 'URL(''') ';
        'Error                  ': 'new 'Error() ';
        'bigint                 ': 'new 'Bigint(0) ';
        'RegExp                 ': 'new 'RegExp(''') ';
        'Int8Array              ': 'new 'Int8Array(0) ';
        'Uint8Array             ': 'new 'Uint8Array(0) ';
        'Uint8ClampedArray      ': 'new 'Uint8ClampedArray(0) ';
        'Int16Array             ': 'new 'Int16Array(0) ';
        'Uint16Array            ': 'new 'Uint16Array(0) ';
        'Uint32Array            ': 'new 'Uint32Array(0) ';
        'BigInt64Array          ': 'new 'BigInt64Array(0) ';
        'BigUint64Array         ': 'new 'BigUint64Array(0) ';
        'Float32Array           ': 'new 'Float32Array(0) ';
        'Float64Array           ': 'new 'Float64Array(0) ';
        'ArrayBuffer            ': 'new 'ArrayBuffer(0) ';
        'SharedArrayBuffer      ': 'new 'SharedArrayBuffer(0) ';
        'FinalizationRegistry   ': 'new 'FinalizationRegistry(() '=> '{}) ';
        'Array                  ': 'new 'Array() ';
        'Map                    ': 'new 'Map() ';
        'Set                    ': 'new 'Set() ';
        'WeakMap                ': 'new 'WeakMap() ';
        'WeakSet                ': 'new 'WeakSet() ';

    skipping [AuditedPropertyMemberDeclaration]
    replace $ [PropertyMemberDeclaration]
        Stmt [STMT] Modifiers [AccessibilityModifier*] PropertyId [Identifier] Nullable [Nullable ?] ': Type [Type] Semi [SEMI]   
    where not 
        ClassBody [initializes PropertyId ClassBody]
    deconstruct * [BuiltinTypeValueMapping] builtinTypeValueMappings
        Type ': MemberExpression [MemberExpression] ';
    construct AuditComment [AuditComment]
        '/* HPAudit: Object properties should be initialized (fixed) : hp-performance-initialize-obj-props : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt Modifiers PropertyId Nullable ': Type '= MemberExpression Semi
end rule

% Detect uninitialized object properties when we don't have the type
rule memberVariableInitializationWarnOnlyWithUnknownType ClassBody [ClassBody] 
    skipping [AuditedPropertyMemberDeclaration]
    replace $ [PropertyMemberDeclaration]
        Stmt [STMT] Modifiers [AccessibilityModifier*] PropertyId [Identifier] Nullable [Nullable ?] TypeAnnotation [TypeAnnotation?] Semi [SEMI]
    where not 
        ClassBody [initializes PropertyId ClassBody]
    construct AuditComment [AuditComment]
        '/* HPAudit: Object properties should be initialized : hp-performance-initialize-obj-props : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt Modifiers PropertyId Nullable TypeAnnotation Semi
end rule

% Check if the object property was initialized in the class constructor
function initializes Identifier [Identifier] ClassBody [ClassBody]
    deconstruct * [ConstructorDeclaration] ClassBody
        ConstructorDeclaration [ConstructorDeclaration]
    deconstruct * [Expression] ConstructorDeclaration
        _ [attr ExpressionType] 'this. Identifier '= _ [AssignmentExpression] 
end function

% Issue #53, Do not use return await

function auditReturnAwait
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-return-await" 1]
    replace [program]
        P [program]
    by
        P [auditDirectReturnAwait]
          [auditIndirectReturnAwait]
end function

rule auditDirectReturnAwait
    skipping [TryStatement]
    replace * [SourceElement*]
        Stmt [STMT] 'return 'await UpdateExpression [UpdateExpression] Semi [SEMI]   
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use return await outside try catch (fixed) : hp-specs-no-return-await : 1 : LINENUMBER : FILEPATH */
    construct AuditCommentStatement [SourceElement]
        AuditComment
    by
        AuditCommentStatement
        Stmt 'return UpdateExpression Semi
        MoreElements
end rule

function auditIndirectReturnAwait
    replace [program]
        P [program]
    by
        P [auditIndirectReturnAwaitInitializer]
          [auditIndirectReturnAwaitAssignment]
end function

rule auditIndirectReturnAwaitInitializer
    skipping [TryStatement]
    replace * [SourceElement*]
        Stmt1 [STMT] VarLetOrConst [VarLetOrConst] AwaitIdentifier [Identifier] Nullable [Nullable?] TypeAnnotation [TypeAnnotation?]  '= 
            ExpressionType [attr ExpressionType] 'await UpdateExpression [UpdateExpression] Semi1 [SEMI]   
        Stmt2 [STMT] 'return Expression [Expression] Semi2 [SEMI]   
        MoreElements [SourceElement*]
    skipping [MemberSelectors]
    deconstruct * [Identifier] Expression
        AwaitIdentifier
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use return await outside try catch (fixed) : hp-specs-no-return-await : 1 : LINENUMBER : FILEPATH */
    construct AuditCommentStatement [SourceElement]
        AuditComment
    by
        AuditCommentStatement
        Stmt1 VarLetOrConst AwaitIdentifier Nullable TypeAnnotation '= ExpressionType UpdateExpression Semi1
        Stmt2 'return Expression Semi2
        MoreElements
end rule

rule auditIndirectReturnAwaitAssignment
    skipping [TryStatement]
    replace * [SourceElement*]
        Stmt1 [STMT] AwaitIdentifier [Identifier] '= ExpressionType [attr ExpressionType] 'await UpdateExpression [UpdateExpression] Semi1 [SEMI]   
        Stmt2 [STMT] 'return Expression [Expression] Semi2 [SEMI]   
        MoreElements [SourceElement*]
    skipping [MemberSelectors]
    deconstruct * [Identifier] Expression
        AwaitIdentifier
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use return await outside try catch (fixed) : hp-specs-no-return-await : 1 : LINENUMBER : FILEPATH */
    construct AuditCommentStatement [SourceElement]
        AuditComment
    by
        AuditCommentStatement
        Stmt1 AwaitIdentifier '= ExpressionType UpdateExpression Semi1
        Stmt2 'return Expression Semi2
        MoreElements
end rule

% Issue #52: Do not end finally block abnormally

rule auditFinallyBlocks
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-finally-end-abnormally" 1]
    replace $ [Finally]
        FinallyBlock [Finally]
    by
        FinallyBlock [auditAbnormalFinalStatement]
end rule

function auditAbnormalFinalStatement
    skipping [SourceElement]
    replace * [SourceElement*]
        AbnormalFinalStatement [Statement]
    where
        AbnormalFinalStatement [isBreak] [isContinue] [isThrow] [isReturn]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not end finally block abnormally : hp-specs-no-finally-end-abnormally : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment AbnormalFinalStatement
end function

function isBreak
    match [Statement]
        _ [BreakStatement]
end function

function isContinue
    match [Statement]
        _ [ContinueStatement]
end function

function isThrow
    match [Statement]
        _ [ThrowStatement]
end function

function isReturn
    match [Statement]
        _ [ReturnStatement]
end function

% Issue #34: Do not create functions dynamically

rule auditDynamicFunctions
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-dynamic-funcs" 1]
    replace [MemberExpression]
        'new 'Function '( Clist [CLIST] Arguments [AssignmentExpressionOrSpreadArgument*] ')
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not create functions dynamically (fixed) : hp-specs-no-dynamic-funcs : 1 : LINENUMBER : FILEPATH */
    % The 1 - n-1'th arguments specify the parameters of the function
    construct Parameters [FormalParameter*]
        _ [createFormalParameter Arguments]
    % The last argument specifies the body of the function
    construct Body [SourceElement*]
        _ [createFunctionBody Arguments]
    by
        AuditComment '( '( Parameters ') '=> '{ Body '} ')
end rule

function createFormalParameter Arguments [AssignmentExpressionOrSpreadArgument*]
    % Get the next argument
    deconstruct Arguments
        Argument [AssignmentExpressionOrSpreadArgument] MoreArguments [AssignmentExpressionOrSpreadArgument*]
    % Which is not the final one
    deconstruct MoreArguments
        _ [AssignmentExpressionOrSpreadArgument] _ [AssignmentExpressionOrSpreadArgument*]
    % Convert it to a formal parameter
    replace [FormalParameter*]
        Parameters [FormalParameter*]
    % Arguments should be formal parameter names, as string literals
    deconstruct Argument
        _ [attr ExpressionType] ArgumentCharlit [charlit] _ [COMMA]
    construct ArgumentId [id]
        _ [unquote ArgumentCharlit]
    % Add the formal name to the list
    construct Parameter [FormalParameter]
        ArgumentId ',
    by
        Parameters [. Parameter] [createFormalParameter MoreArguments] [dropLastComma]
end function

function dropLastComma
    replace * [FormalParameter*]
        LastParameterId [id] ',
    by
        LastParameterId
end function

function createFunctionBody Arguments [AssignmentExpressionOrSpreadArgument*]
    % The last argument specifies the function body
    deconstruct * Arguments
        LastArgument [AssignmentExpressionOrSpreadArgument]
    deconstruct LastArgument
        _ [attr ExpressionType] LastArgumentCharlit [charlit] _ [COMMA]
    % We have to be careful with newlines in [parse]
    construct BodyStatements [SourceElement*]
        _ [pragma "-nonewline"] [parse LastArgumentCharlit] [pragma "-newline"]
    replace [SourceElement*]
        % none
    by
        BodyStatements
end function

% Issue #46: Do not return undefined

rule auditReturnUndefined
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-return-undefined" 1]
    replace $ [FunctionDeclaration]
        Stmt [STMT] Async ['async ?] 'function FunctionId [BindingIdentifier?] 
            CallSignature [CallSignature] 
            FunctionBody [OptionalFunctionBody] 
        Semi [SEMI]
    skipping [TypeParameters] [Parameters]
    deconstruct * [TypeAnnotation] CallSignature
        ': Type [Type]
    deconstruct not Type
        'void
    by
        Stmt Async 'function FunctionId 
            CallSignature [auditReturnUndefinedType] 
            FunctionBody  [auditReturnUndefinedNoValue] 
                          [auditReturnUndefinedValue] 
                          [auditReturnUndefinedNoReturn]
        Semi 
end rule

function auditReturnUndefinedType
    skipping [Parameters]
    replace * [TypeAnnotation]
        ': Type [Type]
    deconstruct * [PrimaryType] Type
        'undefined 
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not return undefined : hp-specs-no-return-undefined : 1 : LINENUMBER : FILEPATH */
    by
        ': AuditComment Type
end function

function auditReturnUndefinedNoValue
    replace [OptionalFunctionBody]
        Block [BLOCK] '{ BodyElements [SourceElement*] '} End [END]
    % Do we return a value?
    deconstruct * [ReturnStatement] BodyElements
        _ [STMT] 'return _ [Expression] _ [SEMI]
    % Audit the ones that don't
    by
        Block '{ BodyElements [auditReturnNoValue] '} End
end function

rule auditReturnNoValue
    replace $ [SourceElement*]
        Stmt [STMT] 'return Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not return undefined : hp-specs-no-return-undefined : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment 
        Stmt 'return Semi 'audited
        MoreElements
end rule

rule auditReturnUndefinedValue
    construct UndefinedExpressions [UntypedAssignmentExpression*]
        'void 0  'undefined
    replace $ [SourceElement*]
        ReturnStatement [ReturnStatement]
        MoreElements [SourceElement*]
    deconstruct ReturnStatement
        _ [STMT] 'return ReturnExpressionType [attr ExpressionType] ReturnExpression [UntypedAssignmentExpression] _ [SEMI]
    construct NormalizedReturnExpression [UntypedAssignmentExpression]
        ReturnExpression [normalizeReturnExpression]
    deconstruct * [UntypedAssignmentExpression] UndefinedExpressions
        NormalizedReturnExpression
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not return undefined : hp-specs-no-return-undefined : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ReturnStatement 'audited
        MoreElements
end rule

function normalizeReturnExpression
    replace [UntypedAssignmentExpression]
        'void _ [UpdateExpression]
    by
        'void 0
end function

rule auditReturnUndefinedNoReturn
    skipping [SourceElement]
    replace $ [SourceElements]
        BodyElements [SourceElement*]
    % Do we not directly return a value in this scope?
    skipping [SourceElement]
    deconstruct not * [SourceElement] BodyElements
        _ [STMT] 'return _ [Expression] _ [SEMI] _ [attr 'audited]
    % And we also don't directly return a value in every subscope of this scope?
    where
        BodyElements [hasNonReturnSubscope]
    % Audit the ones that don't
    deconstruct * [SourceElement] BodyElements
        LastElement [SourceElement]
    deconstruct * [SEMI] LastElement
        LastSemi [SEMI]
    deconstruct * [srclinenumber] LastSemi
        SrcLineNumber [srclinenumber]
    by
        BodyElements [auditNoReturn SrcLineNumber]
end rule

function hasNonReturnSubscope
    skipping [SourceElements]
    match * [SourceElements]
        Subscope [SourceElements]
    deconstruct not * [SourceElement] Subscope
        _ [STMT] 'return _ [Expression?] _ [SEMI] _ [attr 'audited]
end function

function auditNoReturn SrcLineNumber [srclinenumber]
    skipping [SourceElement]
    replace * [SourceElement*]
        % end of scope
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not return undefined : hp-specs-no-return-undefined : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        SrcLineNumber
end function

% Issue #38: Do not use equality operators on floating point data

function auditFloatEquality
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-equality-ops-floating" 1]
    replace [program]
        P [program]
    % Are there floats in this program?
    where
        P [hasFloatLiteralOperand] [hasDivision]
    % Ok then, check it
    by
        P [auditEachFloatEquality]
end function

rule auditEachFloatEquality
    % Assume unique naming for the moment
    replace $ [SourceElements]
        Scope [SourceElement*]
    export Scope
    by
        Scope [auditFloatEqualityExpressions]
end rule

rule auditFloatEqualityExpressions
    % Find every equality expression
    skipping [AuditedEqualityExpression]
    replace $ [EqualityExpression]
        EqualityExpression [EqualityExpression] 
    deconstruct EqualityExpression
        _ [EqualityExpression] _ [EqualityOp] _ [RelationalExpression]
    % That has a float subexpression
    where
        EqualityExpression [hasFloatLiteralOperand]
                           [hasFloatVariableOperand]
                           [hasDivision]
    % Convert it if we can, otherwise just flag it
    by
        EqualityExpression [convertFloatEquality]
                           [noconvertFloatEquality]
end rule

function hasFloatLiteralOperand
    % Does the expression have a float literal in it?
    match * [Literal]
        Number [number]
    where
        Number [grep "."] [grep "e"] [grep "E"]
end function

function hasDivision
    % Does the expression have a division in it?
    skipping [MemberSelectors]
    match * [MultiplicativeOp]
        '/
end function

function hasFloatVariableOperand
    % Does the expression have a float variable reference in it?
    skipping [MemberSelectors]
    match * [Identifier]
        VariableId [Identifier]
    % It's a float variable if it's initialized or assigned a float expression
    import Scope [SourceElement*]
    where
        Scope [initializesFloat VariableId]
              [assignsFloat VariableId]
end function

function initializesFloat VariableId [Identifier]
    % Is the variable (or another variable of the same name) initialized to a float expression?
    skipping [ClassDeclaration]
    match * [SimpleVariableBinding]
        VariableId _ [Nullable?] _ [TypeAnnotation?] '= AssignmentExpression [AssignmentExpression]
    where
        AssignmentExpression [hasFloatLiteralOperand]
                             % [hasFloatVariableOperand Depth VariableId]   % too expensive!
                             [hasDivision]
end function

function assignsFloat VariableId [Identifier]
    % Is the variable (or another variable of the same name) assigned a float expression?
    skipping [ClassDeclaration]
    match * [Assignment]
        VariableId _ [AssignmentOp] AssignmentExpression [AssignmentExpression]
    where
        AssignmentExpression [hasFloatLiteralOperand]
                             % [hasFloatVariableOperand Depth VariableId]   % too expensive!
                             [hasDivision]
end function

function convertFloatEquality
    % If the operands of the equality are arithmetic expressions, 
    % we can automatically convert a === b to Math.abs (a - b) < Number.EPSILON
    replace [EqualityExpression]
        LeftOperand [EqualityExpression] EqualityOp [EqualityOp] RightOperand [RelationalExpression]
    construct ParenthesizedLeftOperand [MultiplicativeExpression]
        '( LeftOperand ')
    construct ParenthesizedRightOperand [MultiplicativeExpression]
        '( RightOperand ')
    construct Subtraction [AdditiveExpression]
        ParenthesizedLeftOperand [unparenthesize] '- ParenthesizedRightOperand [unparenthesize]
    construct Relation [RelationalExpression]
        'Math.abs ( Subtraction ) < 'Number.EPSILON
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use equality operators on floating point data (fixed) : hp-specs-no-equality-ops-floating : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment Relation [invertIfNot EqualityOp]
end function

function unparenthesize
    replace [MultiplicativeExpression]
        '( MultiplicativeExpression [MultiplicativeExpression] ')
    by
        MultiplicativeExpression
end function

function invertIfNot EqualityOp [EqualityOp]
    % If it's an inequality
    construct InequalityOps [EqualityOp*]
        '!= '!==
    where
        EqualityOp [isInequality each InequalityOps]
    % Then invert the relation
    skipping [Arguments]
    replace [RelationalExpression]
        RelationalExpression [RelationalExpression]
    by
        '! '( RelationalExpression ')
end function

function isInequality InequalityOp [EqualityOp]
    match * [EqualityOp]
        InequalityOp
end function

function noconvertFloatEquality
    replace [EqualityExpression]
        EqualityExpression [EqualityExpression]
    deconstruct EqualityExpression
        LeftOperand [EqualityExpression] EqualityOp [EqualityOp] RightOperand [RelationalExpression]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use equality operators on floating point data : hp-specs-no-equality-ops-floating : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment EqualityExpression
end function

% Issue #56: Explicitly declare return types of functions and methods

function auditFunctionReturnTypes
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-explicit-return-types" 1]
    % Infer function types from the inferred types of their returned expressions
    replace [program]
        P [program]
    by
        P [inferredReturnTypes] 
          [voidReturnTypes]
          [unknownReturnTypes]
          % Same for methods
          [inferredMethodReturnTypes] 
          [voidMethodReturnTypes] 
          [unknownMethodReturnTypes]
          % Same for arrow functions
          [inferredArrowReturnTypes] 
          [inferredArrowConciseTypes] 
          [voidArrowReturnTypes] 
          [unknownArrowReturnTypes]
end function

rule inferredReturnTypes
    % Find each untyped function
    replace [SourceElement*]
        Stmt [STMT] Async ['async ?] 'function FunctionId [BindingIdentifier?] Async2 ['async ?] TypeParameters [TypeParameters?] Parameters [Parameters] 
            FunctionBody [OptionalFunctionBody] Semi [SEMI]
        MoreElements [SourceElement*]
    % See if we know the type of any of its return statements
    skipping [FunctionDeclaration]
    deconstruct * [ReturnStatement] FunctionBody
        _ [STMT] 'return '[: Type [Type] '] _ [UntypedAssignmentExpression] _ [SEMI]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods (fixed) : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    construct AuditCommentStatement [SourceElement]
        AuditComment
    % If so, we add its return type
    by
        AuditCommentStatement
        Stmt Async 'function FunctionId Async2 TypeParameters Parameters ': Type FunctionBody Semi
        MoreElements
end rule

rule voidReturnTypes
    % Find each untyped function
    replace [SourceElement*]
        Stmt [STMT] Async ['async ?] 'function FunctionId [BindingIdentifier?] Async2 ['async ?] TypeParameters [TypeParameters?] Parameters [Parameters] 
            FunctionBody [OptionalFunctionBody] Semi [SEMI]
        MoreElements [SourceElement*]
    % That does not have any return statements
    skipping [FunctionDeclaration]
    deconstruct not * [ReturnStatement] FunctionBody
        _ [ReturnStatement]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods (fixed) : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    construct AuditCommentStatement [SourceElement]
        AuditComment
    % If so, it must be void
    by
        AuditCommentStatement
        Stmt Async 'function FunctionId Async2 TypeParameters Parameters ': 'void FunctionBody Semi
        MoreElements
end rule

rule unknownReturnTypes
    % Find each untyped function
    replace [SourceElement*]
        Stmt [STMT] Async ['async ?] 'function FunctionId [BindingIdentifier?] Async2 ['async ?] TypeParameters [TypeParameters?] Parameters [Parameters] 
            FunctionBody [OptionalFunctionBody] Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    construct AuditCommentStatement [SourceElement]
        AuditComment
    % If so, it must be void
    by
        AuditCommentStatement
        Stmt Async 'function FunctionId Async2 TypeParameters Parameters FunctionBody Semi 'audited
        MoreElements
end rule

rule inferredMethodReturnTypes
    % Find each untyped memeber function
    replace [PropertyMemberDeclaration]
        Stmt [STMT] Modifiers [AccessibilityModifier*] Async ['async ?] FunctionId [PropertyName] Nullable [Nullable ?] 
            Async2 ['async ?] TypeParameters [TypeParameters?] Parameters [Parameters] FunctionBody [OptionalFunctionBody] Semi [SEMI]
    % See if we know the type of any of its return statements
    skipping [FunctionDeclaration]
    deconstruct * [ReturnStatement] FunctionBody
        _ [STMT] 'return '[: Type [Type] '] _ [UntypedAssignmentExpression] _ [SEMI]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods (fixed) : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    % If so, we add its return type
    by
        AuditComment
        Stmt Modifiers Async FunctionId Nullable Async2 TypeParameters Parameters ': Type FunctionBody Semi
end rule

rule voidMethodReturnTypes
    % Find each untyped memeber function
    replace [PropertyMemberDeclaration]
        Stmt [STMT] Modifiers [AccessibilityModifier*] Async ['async ?] FunctionId [PropertyName] Nullable [Nullable ?] 
            Async2 ['async ?] TypeParameters [TypeParameters?] Parameters [Parameters] FunctionBody [OptionalFunctionBody] Semi [SEMI]
    % That does not have any return statements
    skipping [FunctionDeclaration]
    deconstruct not * [ReturnStatement] FunctionBody
        _ [ReturnStatement]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods (fixed) : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    % If so, it must be void
    by
        AuditComment
        Stmt Modifiers Async FunctionId Nullable Async2 TypeParameters Parameters ': 'void FunctionBody Semi
end rule

rule unknownMethodReturnTypes
    % Find each untyped memeber function
    skipping [AuditedPropertyMemberDeclaration]
    replace [PropertyMemberDeclaration]
        Stmt [STMT] Modifiers [AccessibilityModifier*] Async ['async ?] FunctionId [PropertyName] Nullable [Nullable ?] 
            Async2 ['async ?] TypeParameters [TypeParameters?] Parameters [Parameters] FunctionBody [OptionalFunctionBody] Semi [SEMI]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt Modifiers Async FunctionId Nullable Async2 TypeParameters Parameters FunctionBody Semi
end rule

rule inferredArrowReturnTypes
    % Find each untyped arrow function
    replace [SimpleVariableBinding]
        FunctionId [BindingIdentifier] Nullable [Nullable?] '= Async ['async ?] Async2 ['async ?] 
            TypeParameters [TypeParameters?] Parameters [Parameters] '=> FunctionBody [ArrowFunctionBody]
    % See if we know the type of any of its return statements
    skipping [FunctionDeclaration]
    deconstruct * [ReturnStatement] FunctionBody
        _ [STMT] 'return '[: Type [Type] '] _ [UntypedAssignmentExpression] _ [SEMI]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods (fixed) : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    % If so, we add its return type
    by
        AuditComment
        FunctionId Nullable ': Parameters '=> Type '= Async Async2 TypeParameters Parameters '=> FunctionBody
end rule

rule inferredArrowConciseTypes
    % Same as above, but for concise arrow functions
    % If we know the type of the return expression
    replace [SimpleVariableBinding]
        FunctionId [BindingIdentifier] Nullable [Nullable?] '= Async ['async ?] Async2 ['async ?] 
            TypeParameters [TypeParameters?] Parameters [Parameters] '=> '[: Type [Type] '] Expression [UntypedAssignmentExpression]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods (fixed) : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    % Then we know its return type
    by
        AuditComment
        FunctionId Nullable ': Parameters '=> Type '= Async Async2 TypeParameters Parameters '=> '[: Type '] Expression
end rule

rule voidArrowReturnTypes
    % Find each untyped arrow function
    replace [SimpleVariableBinding]
        FunctionId [BindingIdentifier] Nullable [Nullable?] '= Async ['async ?] Async2 ['async ?] 
            TypeParameters [TypeParameters?] Parameters [Parameters] '=> FunctionBody [ArrowFunctionBody]
    % That does not have any return statements
    skipping [FunctionDeclaration]
    deconstruct not * [ReturnStatement] FunctionBody
        _ [ReturnStatement]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods (fixed) : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    % If so, it must be void
    by
        AuditComment
        FunctionId Nullable ': Parameters '=> 'void '= Async Async2 TypeParameters Parameters '=> FunctionBody
end rule

rule unknownArrowReturnTypes
    % Find each untyped arrow function
    skipping [AuditedSimpleVariableBinding]
    replace [SimpleVariableBinding]
        FunctionId [BindingIdentifier] Nullable [Nullable?] '= Async ['async ?] Async2 ['async ?] 
            TypeParameters [TypeParameters?] Parameters [Parameters] '=> FunctionBody [ArrowFunctionBody]
    construct AuditComment [AuditComment]
        '/* HPAudit: Explicitly declare return types of functions and methods : hp-specs-explicit-return-types : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        FunctionId Nullable '= Async Async2 TypeParameters Parameters '=> FunctionBody
end rule

% Issue #29 : Arguments much match function parameters

rule auditFunctionArgumentTypes
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-args-match-params" 1]
    % Find each function declaration
    replace $ [SourceElement*]
        FunctionDeclaration [SourceElement]
        MoreElements [SourceElement*]
    deconstruct FunctionDeclaration
        _ [STMT] _ ['async ?] 'function FunctionId [Identifier] _ ['async ?] _ [TypeParameters?] '( Clist [CLIST] Parameters [FormalParameter*] ') 
            _ [TypeAnnotation?] _ [OptionalFunctionBody] _ [SEMI]
    % Audit all calls to it
    by
        FunctionDeclaration [matchFunctionCallArgumentTypes FunctionId Parameters]    % recursion ?
        MoreElements        [matchFunctionCallArgumentTypes FunctionId Parameters]
end rule 

rule matchFunctionCallArgumentTypes FunctionId [Identifier] Parameters [FormalParameter*]  
    % For every call to the function
    replace $ [MemberExpression]
        FunctionId TypeArguments [TypeArguments?] '( Clist [CLIST] Arguments [AssignmentExpressionOrSpreadArgument*] ') 
            MemberSelectors [MemberSelector*]
    % Check for a different number of arguments, or arguments whose type doesn't match
    where
        Arguments [differentNumberOf Parameters]
                  [doNotMatchType each Parameters Arguments]
    construct AuditComment [AuditComment]
        '/* HPAudit: Arguments must match function parameters : hp-performance-args-match-params : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        FunctionId TypeArguments '( Clist Arguments ') MemberSelectors
end rule

function differentNumberOf Parameters [FormalParameter*]
    % Spread parameters can have any number of arguments
    deconstruct not * [Spread] Parameters
        _ [Spread]
    % Does this function call have a different number of arguments than the number of parameters?
    match [AssignmentExpressionOrSpreadArgument*]
        Arguments [AssignmentExpressionOrSpreadArgument*]
    % Spread arguments cover many parameters
    deconstruct not * [Spread] Arguments
        _ [Spread]
    % Do we have the same number of arguments as parameters?
    construct NArguments [number]
        _ [length Arguments]
    construct NFormals [number]
        _ [length Parameters]
    deconstruct not NArguments
        NFormals
end function

function doNotMatchType Parameter [FormalParameter] Argument [AssignmentExpressionOrSpreadArgument]
    % For each matching parameter and argument
    deconstruct Parameter
        _ [AccessibilityModifier*] _ [BindingIdentifier]  _ [Nullable?] ': FormalType [Type]  _ [Initializer?] _ [COMMA]
    % Do we know the argument type?
    deconstruct Argument
        '[: ArgumentType [Type] '] _ [UntypedAssignmentExpression] _ [COMMA]
    % If so, is it different from the formal parameter type?
    deconstruct not FormalType
        ArgumentType
end function

% Issue #20 : Arguments much match constructor parameters

rule auditConstructorArgumentTypes
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-args-match-params" 1]
    % Same as above, but for class constructors
    % For each class declaration
    replace $ [SourceElement*]
        ClassDeclaration [SourceElement]
        MoreElements [SourceElement*]
    deconstruct ClassDeclaration
        _ [STMT] _ [AccessibilityModifier*] 'class ClassId [Identifier] _ [TypeParameters?] _ [ClassHeritage]
        _ [BLOCK] '{ ClassBody [ClassBody?] '} _ [END] _ [SEMI]
    % That has an explicit constructor
    skipping [ClassDeclaration]
    deconstruct * [ConstructorDeclaration] ClassBody
        _ [STMT] _ [AccessibilityModifier*] 'constructor '( Clist [CLIST] Parameters [FormalParameter*] ')
            _ [OptionalFunctionBody] _ [SEMI]
    % Check that arguments to calls to the constructor match its formal parameters
    by
        ClassDeclaration
        MoreElements [matchFunctionCallArgumentTypes ClassId Parameters]
end rule 

% Issue #113 : Arguments must match method parameters

function auditMethodArgumentTypes
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-args-match-params" 1]
    replace [program]
        Scope [SourceElement*]
    export Scope
    by
        Scope [auditClassMethodArgumentTypes]
end function

rule auditClassMethodArgumentTypes
    % For each class declaration
    replace $ [SourceElement*]
        ClassDeclaration [SourceElement]
        MoreElements [SourceElement*]
    deconstruct ClassDeclaration
        _ [STMT] _ [AccessibilityModifier*] 'class ClassId [Identifier] _ [TypeParameters?] _ [ClassHeritage] _ [BLOCK] '{ 
            ClassBody [ClassBody?] 
        '} _ [END] _ [SEMI]

    % Get its member function headers
    export MemberFunctions [MemberFunctionHeader*]
        _ % empty
    where
        ClassBody [hasMemberFunctions]
    import MemberFunctions

    % Check that the arguments of all calls to the member functions match their formal parameters
    by
        ClassDeclaration [matchInternalMemberFunctionCalls each MemberFunctions]
        MoreElements     [matchExternalStaticMemberFunctionCalls  ClassId each MemberFunctions]
                         [matchExternalDynamicMemberFunctionCalls ClassId each MemberFunctions]
end rule 

rule hasMemberFunctions
    % Get all member function headers in the class
    skipping [ClassDeclaration]
    match $ [MemberFunctionHeader]
        MemberFunction [MemberFunctionHeader]
    import MemberFunctions [MemberFunctionHeader*]
    export MemberFunctions
        MemberFunctions [. MemberFunction]
end rule

function matchInternalMemberFunctionCalls MemberFunction [MemberFunctionHeader]
    % Member functions can be called inside the class as well as outside
    deconstruct MemberFunction
        _ [AccessibilityModifier*] _ ['async ?] MemberFunctionId [Identifier] _ [Nullable ?] _ [TypeParameters?]
            '( _ [CLIST] Parameters [FormalParameter*] ') _ [TypeAnnotation?]
    replace [SourceElement]
        ClassDeclaration [SourceElement]
    % Check that the arguments of internal calls to member functions match its formal parameters
    by
        ClassDeclaration [matchFunctionCallArgumentTypes MemberFunctionId Parameters]
end function

rule matchExternalStaticMemberFunctionCalls ClassId [Identifier] MemberFunction [MemberFunctionHeader]
    % Member functions may be static, called as members of the class
    deconstruct MemberFunction
        _ [AccessibilityModifier*] _ ['async ?] MemberFunctionId [Identifier] _ [Nullable ?] _ [TypeParameters?]
            '( _ [CLIST] Parameters [FormalParameter*] ') _ [TypeAnnotation?]
    % Find every such static member function call
    replace $ [MemberExpression]
        ClassId '. MemberFunctionId TypeArguments [TypeArguments?] '( Clist [CLIST] Arguments [AssignmentExpressionOrSpreadArgument*] ') 
            MemberSelectors [MemberSelector*]
    % Check for a different number of arguments, or arguments whose type doesn't match
    where
        Arguments [differentNumberOf Parameters]
                  [doNotMatchType each Parameters Arguments]
    construct AuditComment [AuditComment]
        '/* HPAudit: Arguments must match method parameters : hp-performance-args-match-params : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ClassId '. MemberFunctionId TypeArguments '( Clist Arguments ') MemberSelectors
end rule

rule matchExternalDynamicMemberFunctionCalls ClassId [Identifier] MemberFunction [MemberFunctionHeader]
    % Member functions may also be dynamic, called as members of object of the class
    deconstruct MemberFunction
        _ [AccessibilityModifier*] _ ['async ?] MemberFunctionId [Identifier] _ [Nullable ?] _ [TypeParameters?]
            '( _ [CLIST] Parameters [FormalParameter*] ') _ [TypeAnnotation?]
    % Find every such dynamic object member function call
    replace $ [MemberExpression]
        ObjectId [Identifier] '. MemberFunctionId TypeArguments [TypeArguments?] 
            '( Clist [CLIST] Arguments [AssignmentExpressionOrSpreadArgument*] ') MemberSelectors [MemberSelector*]
    % Where the object is declared or assigned the class type
    where
        ObjectId [isDeclaredAs ClassId]
                 [isAssignedAs ClassId]
    % Check for a different number of arguments, or arguments whose type doesn't match
    where
        Arguments [differentNumberOf Parameters]
                  [doNotMatchType each Parameters Arguments]
    construct AuditComment [AuditComment]
        '/* HPAudit: Arguments must match method parameters : hp-performance-args-match-params : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ObjectId '. MemberFunctionId TypeArguments '( Clist Arguments ') MemberSelectors
end rule

function isDeclaredAs ClassId [Identifier]
    % Is this object initialized to an object of the class type?
    match [Identifier]
        ObjectId [Identifier]
    import Scope [SourceElement*]
    deconstruct * [SimpleVariableBinding] Scope
        ObjectId _ [Nullable?] _ [TypeAnnotation?] '= '[: ClassId '] _ [UntypedAssignmentExpression]
end function

function isAssignedAs ClassId [Identifier]
    % Is this object assigned an object of the class type?
    match [Identifier]
        ObjectId [Identifier]
    import Scope [SourceElement*]
    deconstruct * [Assignment] Scope
        ObjectId '= '[: ClassId '] _ [UntypedAssignmentExpression]
end function

% Issue #65 : Name literal constants and enum values in uppercase, words separated by underscores

rule auditConstantNames
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-enum-ucase-uscore" 1]
    % Find every const declaration that has a literal value
    replace $ [SourceElement*]
        ConstAndScope [SourceElement*]
    deconstruct ConstAndScope
        _ [STMT] 'const ConstantId [id] _ [Nullable?] _ [TypeAnnotation?] '= _ [attr ExpressionType] Literal [Literal] _ [SEMI]
        MoreScope [SourceElement*]
    % See if its name needs fixing
    construct AuditedConstantId [id]
        ConstantId [camelCaseToUnderscore 1]
                   [toupper]
    deconstruct not AuditedConstantId
        ConstantId
    % If so, replace its name and all its references
    construct AuditComment [AuditComment]
        '/* HPAudit: Name literal constants in uppercase, words separated by underscores (fixed) : hp-specs-enum-ucase-uscore : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ConstAndScope [replaceId ConstantId AuditedConstantId]
end rule

rule replaceId OldId [id] NewId [id]
    % Don't mistake member names for references to the constant name
    skipping [FieldIdentifier]
    replace [id]
        OldId
    by
        NewId
end rule

function camelCaseToUnderscore Position [number]
    % Recursively iterate through the identifier, looking for "aA" to turn into "a_A"
    replace [id]
        Id [id]
    construct LengthId [number]
        _ [# Id]
    where
        Position [< LengthId]
    construct PositionP1 [number]
        Position [+ 1]
    construct NewId [id]
        Id [convertCamelCaseToUnderscore Position]
    by
        NewId [camelCaseToUnderscore PositionP1]
end function

function convertCamelCaseToUnderscore Position [number]
    % Is there an "aA" at position Position?
    replace [id]
        Id [id]
    construct PositionP1 [number]
        Position [+ 1]
    % Get the two characters at position Position
    construct Id12 [stringlit]
        _ [+ Id] [: Position PositionP1]
    % Is the first one lower case, e.g. "a"?
    construct Id1 [stringlit]
        Id12 [: 1 1]
    where all
        Id1 [>= "a"] [<= "z"]
    % And the second one uppercase, e.g. "A"?
    construct Id2 [stringlit]
        Id12 [: 2 2]
    where all
        Id2 [>= "A"] [<= "Z"]
    % Then replace them by "a_A"
    construct Id1USId2 [stringlit]
        _ [+ Id1] [+ "_"] [+ Id2]
    construct NewIdString [stringlit]
        _ [+ Id] [subst Id12 Id1USId2] 
    construct NewId [id]
        _ [+ NewIdString]
    by
        NewId
end function

rule auditEnumNames
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-enum-ucase-uscore" 1]
    % Same as above, but for enum member names
    replace $ [SourceElement*]
        Stmt [STMT] Const ['const ?] 'enum EnumId [Identifier] Block [BLOCK] '{ EnumMembers [EnumMembers] '} End [END] Semi [SEMI]
        Scope [SourceElement*]
    % Get all the enum member names
    construct EnumMemberNames [PropertyName*]
        _ [^ EnumMembers]
    construct EnumMemberIds [id*]
        _ [^ EnumMemberNames]
    % Convert them all as above
    construct AuditedEnumMemberIds [id*]
        EnumMemberIds [auditEnumMemberNames]
    deconstruct not AuditedEnumMemberIds
        EnumMemberIds
    % If they are different, then replace them and all their references
    construct AuditComment [AuditComment]
        '/* HPAudit: Name enum members in uppercase, words separated by underscores (fixed) : hp-specs-enum-ucase-uscore : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt Const 'enum EnumId Block '{ EnumMembers [$ each EnumMemberIds AuditedEnumMemberIds] '} End Semi
        Scope [replaceEnumMemberReferences EnumId each EnumMemberIds AuditedEnumMemberIds]
end rule
    
rule auditEnumMemberNames
    % Convert each enum member id
    replace $ [id]
        EnumMemberId [id]
    construct AuditedEnumMemberId [id]
        EnumMemberId [camelCaseToUnderscore 1]
                     [toupper]
    by
        AuditedEnumMemberId
end rule

rule replaceEnumMemberReferences EnumId [Identifier] EnumMemberId [id] AuditedEnumMemberId [id]
    % Replace all references to the new enum member name
    replace $ [MemberExpression]
        EnumId '. EnumMemberId
    by
        EnumId '. AuditedEnumMemberId
end rule

% Issue #44 : Use strict equality operators

rule auditEqualityOperators
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-strict-equality-ops" 1]
    % Replace unstrict equality ops with strict
    construct UnstrictEqualityOps [EqualityOp*]
        '== '!=
    construct StrictEqualityOps [EqualityOp*]
        '=== '!==
    % Find each equality
    replace $ [Equality]
        LeftOperand [EqualityExpression] EqualityOp [EqualityOp] RightOperand [RelationalExpression]
    % That's not comparing to null
    deconstruct not RightOperand
        'null
    % Is it unstrict?
    deconstruct * [EqualityOp] UnstrictEqualityOps
        EqualityOp
    % Then strict it!
    construct AuditComment [AuditComment]
        '/* HPAudit: Use strict equality operators (fixed) : hp-specs-strict-equality-ops : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        LeftOperand EqualityOp [$ each UnstrictEqualityOps StrictEqualityOps] RightOperand
end rule

% Issue #66 : Do not use negative boolean variable and function names

rule auditBooleanNames
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-negative-bool-vars-funcs" 1]
    replace [SourceElement*]
        BooleanDeclaration [VariableOrFunctionDeclaration]
        MoreElements [SourceElement*]
    skipping [Parameters] [SourceElements]
    deconstruct * [Type] BooleanDeclaration
        'boolean
    skipping [Parameters] [SourceElements]
    deconstruct * [BindingIdentifier] BooleanDeclaration
        BooleanId [id]
    construct BooleanIdString [stringlit]
        _ [+ BooleanId]
    where
        BooleanIdString [grep "Not"] [egrep "^no"]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use negative boolean variable and function names : hp-specs-no-negative-bool-vars-funcs : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        BooleanDeclaration 'audited
        MoreElements 
end rule

% Issue64 : Use lowerCamelCase for variable, function and parameter names

function auditVariableAndFunctionNames
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-lcamelcase-vars-funcs-params" 1]
    replace [program]
        P [program]
    by
        P [auditSourceVariableAndFunctionNames]
          [auditMemberVariableAndFunctionNames]
end function

rule auditSourceVariableAndFunctionNames
    replace $ [SourceElement*]
        Decorators [DecoratorList?] VariableOrFunctionDeclaration [VariableOrFunctionDeclaration]
        MoreElements [SourceElement*]
    skipping [Parameters] [SourceElements]
    deconstruct not * [VarLetOrConst] VariableOrFunctionDeclaration
        'const 
    skipping [Parameters] [SourceElements]
    deconstruct * [BindingIdentifier] VariableOrFunctionDeclaration
        VariableOrFunctionId [id]
    where 
        VariableOrFunctionId [startsWithUpper] [grep "_"]
    construct AuditedVariableOrFunctionId [id]
        VariableOrFunctionId [underscoreToCamelCase]
                             [tolower1]
    deconstruct not AuditedVariableOrFunctionId
        VariableOrFunctionId
    construct AuditComment [AuditComment]
        '/* HPAudit: Use lowerCamelCase for variable and function names (fixed) : hp-specs-lcamelcase-vars-funcs-params : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Decorators VariableOrFunctionDeclaration [replaceId VariableOrFunctionId AuditedVariableOrFunctionId]
        MoreElements [replaceId VariableOrFunctionId AuditedVariableOrFunctionId]
end rule

rule auditMemberVariableAndFunctionNames
    replace $ [ClassElement*]
        Decorators [DecoratorList?]  MemberVariableOrFunctionDeclaration [PropertyMemberDeclaration]
        MoreElements [ClassElement*]
    deconstruct * [PropertyName] MemberVariableOrFunctionDeclaration
        MemberVariableOrFunctionId [id]
    where 
        MemberVariableOrFunctionId [startsWithUpper] [isAllUpper] [grep "_"]
    construct AuditComment [AuditComment]
        '/* HPAudit: Use lowerCamelCase for variable and function names : hp-specs-lcamelcase-vars-funcs-params : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Decorators MemberVariableOrFunctionDeclaration 'audited
        MoreElements
end rule

function startsWithUpper
    match [id]
        Id [id]
    construct Id1 [id]
        Id [: 1 1]
    where all
        Id1 [>= "A"] [<= "Z"]
end function

function startsWithLower
    match [id]
        Id [id]
    construct Id1 [id]
        Id [: 1 1]
    where all
        Id1 [>= "a"] [<= "z"]
end function

function isAllUpper
    construct Lowers [id*]
        'a 'b 'c 'd 'e 'f 'g 'h 'i 'j 'k 'l 'm 'n 'o 'p 'q 'r 's 't 'u 'v 
    match [id]
        Id [id]
    construct LengthId [number]
        _ [# Id]
    where
        LengthId [> 1]
    where not
        Id [grep each Lowers]
end function

function auditParameterNames
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-lcamelcase-vars-funcs-params" 1]
    replace [program]
        P [program]
    by
        P [auditFunctionParameterNames]
          [auditMemberFunctionParameterNames]
          [auditConstructorParameterNames]
end function

rule auditFunctionParameterNames
    % Find each function declaration
    replace $ [FunctionDeclaration]
        FunctionDeclaration [FunctionDeclaration]
    deconstruct FunctionDeclaration
        _ [STMT] _ ['async ?] 'function _ [Identifier] _ ['async ?] _ [TypeParameters?] '( _ [CLIST] Parameters [FormalParameter*] ') _ [TypeAnnotation?]
            _ [OptionalFunctionBody] _ [SEMI]
    by
        FunctionDeclaration [auditParameterName each Parameters]
end rule

rule auditMemberFunctionParameterNames
    % Find each member function declaration
    replace $ [MemberFunctionDeclaration]
        MemberFunctionDeclaration [MemberFunctionDeclaration]
    deconstruct MemberFunctionDeclaration
        _ [STMT] _ [AccessibilityModifier*] _ ['async ?] _ [Identifier] _ [Nullable ?] _ [TypeParameters?]
            '( _ [CLIST] Parameters [FormalParameter*] ') _ [TypeAnnotation?] _ [OptionalFunctionBody] _ [SEMI]
    by
        MemberFunctionDeclaration [auditParameterName each Parameters]
end rule

rule auditConstructorParameterNames
    % Find each constructor declaration
    replace $ [ConstructorDeclaration]
        ConstructorDeclaration [ConstructorDeclaration]
    deconstruct ConstructorDeclaration
        _ [STMT] _ [AccessibilityModifier*] 'constructor '( _ [CLIST] Parameters [FormalParameter*] ') _ [OptionalFunctionBody] _ [SEMI]
    by
        ConstructorDeclaration [auditParameterName each Parameters]
end rule

function auditParameterName FormalParameter [FormalParameter]
    deconstruct * [BindingIdentifier] FormalParameter
        FormalParameterId [id]
    where
        FormalParameterId [startsWithUpper] [grep "_"]
    construct AuditedFormalParameterId [id]
        FormalParameterId [underscoreToCamelCase]
                          [tolower1]
    deconstruct not AuditedFormalParameterId
        FormalParameterId
    construct AuditComment [AuditComment]
        '/* HPAudit: Use lowerCamelCase for parameter names (fixed) : hp-specs-lcamelcase-vars-funcs-params : 1 : LINENUMBER : FILEPATH */
    construct AuditedFormalParameter [FormalParameter]
        AuditComment FormalParameter
    replace [any]
        FunctionDeclaration [any]
    by
        FunctionDeclaration [$ FormalParameter AuditedFormalParameter] 
                            [replaceId FormalParameterId AuditedFormalParameterId] 
end function

% Issue #63 : Use UpperCamelCase for class, enum and namespace names

rule auditClassEnumAndNamespaceNames
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-ucamelcase-cls-enums-ns" 1]
    replace $ [SourceElement*]
        ClassEnumOrNamespaceDeclaration [ClassOrNamespaceDeclaration]
        MoreElements [SourceElement*]
    deconstruct * [BindingIdentifier] ClassEnumOrNamespaceDeclaration
        ClassEnumOrNamespaceId [id]
    where
        ClassEnumOrNamespaceId [startsWithLower] [isAllUpper] [grep "_"]
    construct AuditedClassEnumOrNamespaceId [id]
        ClassEnumOrNamespaceId [underscoreToCamelCase]
                               [toupper1]
                               [tolowerN]
    deconstruct not AuditedClassEnumOrNamespaceId
        ClassEnumOrNamespaceId
    construct AuditComment [AuditComment]
        '/* HPAudit: Use UpperCamelCase for class, enum and namespace names (fixed) : hp-specs-ucamelcase-cls-enums-ns : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ClassEnumOrNamespaceDeclaration [replaceId ClassEnumOrNamespaceId AuditedClassEnumOrNamespaceId]
        MoreElements [replaceId ClassEnumOrNamespaceId AuditedClassEnumOrNamespaceId]
end rule

function underscoreToCamelCase
    replace [id]
        UnderscoreId [id]
    % under_score
    construct UnderscoreIndex [number]
        _ [index UnderscoreId "_"] 
    % not the leading _ (e.g., _this)
    where 
        UnderscoreIndex [> 1]
    construct UnderscoreIndexMinus1 [number]
        UnderscoreIndex [- 1]
    % under
    construct PreUnderscore [id]
        UnderscoreId [: 1 UnderscoreIndexMinus1]
    construct UnderscoreIndexPlus1 [number]
        UnderscoreIndex [+ 1]
    % score
    construct PostUnderscore [id]
        UnderscoreId [: UnderscoreIndexPlus1 999]
                     [toupper1]
    by
        PreUnderscore [+ PostUnderscore]
                      [underscoreToCamelCase]
end function

function toupper1
    replace [id]
        Id [id]
    construct Id1 [id]
        Id [: 1 1]
    construct Id2N [id]
        Id [: 2 999]
    by
        Id1 [toupper] [+ Id2N]
end function

function tolower1
    replace [id]
        Id [id]
    construct Id1 [id]
        Id [: 1 1]
    where all
        Id1 [>= "A"] [<= "Z"]
    construct Id2N [id]
        Id [: 2 999] [tolower1]
    by
        Id1 [tolower] [+ Id2N]
end function

function tolowerN
    replace [id]
        Id [id]
    where
        Id [isAllUpper]
    by
        Id [tolower] [toupper1]
end function

% Issue #48 : Do not assign this to a variable

function auditAssignThis
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-this-to-vars" 1]
    replace [program]
        P [program]
    by
        P [auditThisAssignments]
          [auditThisInitializers]
end function

rule auditThisAssignments
    replace $ [SourceElement*]
        Stmt [STMT] LeftHandSideExpression [LeftHandSideExpression] '= 'this Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign this to a variable : hp-specs-no-this-to-vars : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt LeftHandSideExpression '= 'this Semi 'audited
        MoreElements
end rule

rule auditThisInitializers
    replace $ [SourceElement*]
        Stmt [STMT] VarLetOrConst [VarLetOrConst] BindingIdentifier [BindingIdentifier] Nullable [Nullable?] TypeAnnotation [TypeAnnotation?] '= 'this Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign this to a variable : hp-specs-no-this-to-vars : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt VarLetOrConst BindingIdentifier Nullable TypeAnnotation '= 'this Semi 'audited
        MoreElements
end rule

% Issue #47 : Use rest parameters instead of arguments object

rule auditArgumentsParameter
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-use-rest-params" 1]
    skipping [AuditedPrimaryExpression]
    replace $ [PrimaryExpression]
        'arguments
    construct AuditComment [AuditComment]
        '/* HPAudit: Use rest parameters instead of arguments object : hp-specs-use-rest-params : 1 : LINENUMBER : FILEPATH */
    construct AuditedPrimaryExpression [PrimaryExpression]  
        AuditComment
        'arguments
    by
        AuditedPrimaryExpression
end rule

% Issue #50 : hp-specs-no-modify-prototypes : Do not modify prototypes of built in objects

rule auditPrototypeAssignments
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-modify-prototypes" 1]
    replace $ [SourceElement*]
        Stmt [STMT] LeftHandSideExpression [LeftHandSideExpression] '= AssignmentExpression [AssignmentExpression] Semi [SEMI]
        MoreElements [SourceElement*]
    deconstruct LeftHandSideExpression
        ClassId [Identifier] '. 'prototype '. FieldId [FieldIdentifier]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not modify prototypes : hp-specs-no-modify-prototypes : 1 : LINENUMBER : FILEPATH */
    by 
        AuditComment
        Stmt LeftHandSideExpression '= AssignmentExpression Semi 'audited
        MoreElements
end rule

% Issue 57 : Types and interfaces should be exported as 'type'

function auditTypeExports
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-use-type-exports" 1]
    replace [program]
        P [program]
    by
        P [auditEachTypeExport P]
          [auditMixedTypeExports]
end function

rule auditEachTypeExport P [program]
    replace $ [ExportSpecifier]
        ExportedId [Identifier]
    where
        ExportedId [isTypeAliasName P] [isInterfaceName P]
    construct AuditComment [AuditComment]
        '/* HPAudit: Types and interfaces should be exported as 'type' (fixed) : hp-specs-use-type-exports : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        'type ExportedId
end rule  

function isTypeAliasName P [program]
    match [Identifier]
        Id [Identifier]
    deconstruct * [TypeAliasDeclaration] P
        _ [STMT] 'type Id _ [TypeParameters?] '= _ [Type] _ [SEMI]
end function

function isInterfaceName P [program]
    match [Identifier]
        Id [Identifier]
    deconstruct * [InterfaceDeclaration] P
        _ [STMT] 'interface Id _ [TypeParameters?] _ [ExtendsClause?] _ [BLOCK] '{ _ [InterfaceBody] '} _ [END] _ [SEMI]
end function

rule auditMixedTypeExports 
    replace $ [SourceElement*]
        Stmt [STMT] 'export '{ Clist [CLIST] ExportedItems [ExportSpecifier*] '} End [SEMI]
        MoreElements [SourceElement*]
    construct NonTypeExportedItems [ExportSpecifier*]
        _ [onlyNonTypeItems each ExportedItems]
    construct TypeExportedItems [ExportSpecifier*]
        _ [onlyTypeItems each ExportedItems]
    deconstruct not TypeExportedItems
        % empty
    construct NonTypeExports [SourceElement*]
        _ [makeNonTypeExport NonTypeExportedItems]
    construct TypeExports [SourceElement*]
        Stmt 'export 'type '{ TypeExportedItems '} End
    by
        NonTypeExports 
        [. TypeExports]
        [. MoreElements]
end rule

function makeNonTypeExport NonTypeExportedItems [ExportSpecifier*]
    deconstruct not NonTypeExportedItems
        % none
    replace * [SourceElement*]
        % empty
    construct AuditComment [AuditComment]
        '/* HPAudit: Types and interfaces should be exported separately from classes and objects (fixed) : hp-specs-use-type-exports : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        'export '{ NonTypeExportedItems '} ';
end function

function onlyNonTypeItems ExportedItem [ExportSpecifier]
    deconstruct ExportedItem
        ExportedId [Identifier]
    replace * [ExportSpecifier*]
    by
        ExportedId
end function

function onlyTypeItems ExportedItem [ExportSpecifier]
    deconstruct ExportedItem
        AuditComment [AuditComment?] 'type ExportedId [Identifier]
    replace * [ExportSpecifier*]
    by
        AuditComment ExportedId
end function

% Issue 58 : Types and interfaces should be imported as 'type'

function auditTypeImports
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-use-type-imports" 1]
    replace [program]
        P [program]
    by
        P [auditEachTypeImport P]
          [auditMixedTypeImports]
end function

rule auditEachTypeImport P [program]
    replace $ [ImportSpecifier]
        ImportedId [Identifier]
    where
        ImportedId [isTypeName P]
    construct AuditComment [AuditComment]
        '/* HPAudit: Types and interfaces should be imported as 'type' (fixed) : hp-specs-use-type-imports : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        'type ImportedId
end rule  

function isTypeName P [program]
    match [Identifier]
        Id [Identifier]
    deconstruct * [TypeName] P
        Id
end function

rule auditMixedTypeImports 
    replace $ [SourceElement*]
        Stmt [STMT] 'import '{ ImportedItems [list ImportSpecifier] '} FromClause [FromClause] End [SEMI]
        MoreElements [SourceElement*]
    construct NonTypeImportedItems [list ImportSpecifier]
        _ [onlyNonTypeImportedItems each ImportedItems]
    construct TypeImportedItems [list ImportSpecifier]
        _ [onlyTypeImportedItems each ImportedItems]
    deconstruct not TypeImportedItems
        % empty
    construct NonTypeImports [SourceElement*]
        _ [makeNonTypeImport NonTypeImportedItems FromClause]
    construct TypeImports [SourceElement*]
        Stmt 'import 'type '{ TypeImportedItems '} FromClause End
    by
        NonTypeImports 
        [. TypeImports]
        [. MoreElements]
end rule

function makeNonTypeImport NonTypeImportedItems [list ImportSpecifier] FromClause [FromClause]
    deconstruct not NonTypeImportedItems
        % none
    replace * [SourceElement*]
        % empty
    construct AuditComment [AuditComment]
        '/* HPAudit: Types and interfaces should be imported separately from classes and objects (fixed) : hp-specs-use-type-imports : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        'import '{ NonTypeImportedItems '} FromClause ';
end function

function onlyNonTypeImportedItems ImportedItem [ImportSpecifier]
    deconstruct ImportedItem
        ImportedId [Identifier]
    replace * [list ImportSpecifier]
    by
        ImportedId
end function

function onlyTypeImportedItems ImportedItem [ImportSpecifier]
    deconstruct ImportedItem
        AuditComment [AuditComment?] 'type ImportedId [Identifier]
    replace * [list ImportSpecifier]
    by
        AuditComment ImportedId
end function

% Issue #57 : Export as 'type' is not supported

% obsolete
%(
function auditTypeExports
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-use-type-exports" 1]
    replace [program]
        P [program]
    by
        P [auditEachTypeExports]
          [auditGroupTypeExports]
end function

rule auditEachTypeExports 
    replace $ [SourceElement*]
        Stmt [STMT] 'export '{ ExportedItems [list ExportSpecifier] '} Semi [SEMI]
        MoreElements [SourceElement*]
    deconstruct * [ExportSpecifier] ExportedItems
        'type _ [Identifier]
    construct AuditComment [AuditComment]
        '/* HPAudit: Export as 'type' is not supported in ArkTS (fixed) : hp-specs-use-type-exports : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'export '{ ExportedItems [removeExportTypeSpecifiers] '} Semi
end rule

rule removeExportTypeSpecifiers
    replace $ [ExportSpecifier]
        'type ExportedId [Identifier]
    by
        ExportedId
end rule  

rule auditGroupTypeExports 
    replace $ [SourceElement*]
        Stmt [STMT] 'export 'type '{ ExportedItems [list ExportSpecifier] '} Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Export as 'type' is not supported in ArkTS (fixed) : hp-specs-use-type-exports : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'export '{ ExportedItems '} Semi
        MoreElements
end rule
)%

% Issue #58 : Import as 'type' not supported

% obsolete
%(
function auditTypeImports
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-use-type-imports" 1]
    replace [program]
        P [program]
    by
        P [auditEachTypeImports]
          [auditGroupTypeImports]
end function

rule auditEachTypeImports
    replace $ [SourceElement*]
        Stmt [STMT] 'import '{ ImportedItems [list ImportSpecifier] '} FromClause [FromClause] Semi [SEMI]
        MoreElements [SourceElement*]
    deconstruct * [ImportSpecifier] ImportedItems
        'type _ [Identifier]
    construct AuditComment [AuditComment]
        '/* HPAudit: Import as 'type' is not supported in ArkTS (fixed) : hp-specs-use-type-imports : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'import '{ ImportedItems [removeImportTypeSpecifiers] '} FromClause Semi
end rule

rule removeImportTypeSpecifiers
    replace $ [ImportSpecifier]
        'type ImportedId [Identifier]
    by
        ImportedId
end rule  

rule auditGroupTypeImports
    replace $ [SourceElement*]
        Stmt [STMT] 'import 'type '{ ImportedItems [list ImportSpecifier] '} FromClause [FromClause] Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Import as 'type' is not supported in ArkTS (fixed) : hp-specs-use-type-imports : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'import '{ ImportedItems '} FromClause Semi
        MoreElements
end rule
)%

% Issue #24 : Avoid using closures

rule auditClosures
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-no-closures" 1]
    replace $ [SourceElements]
        Scope [SourceElement*]
    export Scope
    by
        Scope [auditEachFunctionClosures]
end rule

rule auditEachFunctionClosures
    replace $ [SourceElement*]
        FunctionDeclaration [FunctionDeclaration]
        More [SourceElement*]
    % Do we have an unassigned closure?
    where
        FunctionDeclaration [hasGlobalVariableReference FunctionDeclaration]
    % Then suggest a parameter
    construct AuditComment [AuditComment]
        '/* HPAudit: Avoid using closures - use parameters instead : hp-performance-no-closures : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        FunctionDeclaration 'audited
        More
end rule

function hasGlobalVariableReference FunctionDeclaration [FunctionDeclaration]
    % If we have a variable reference
    match * [MemberExpression]
        VariableId [Identifier] MemberSelectors [MemberSelector*]
    % That's not a function
    deconstruct not MemberSelectors
        Arguments [Arguments] _ [MemberSelector*]
    % And is not a literal constant 
    where not
        VariableId [isLiteralConstId]
    % And is not declared in the function
    deconstruct not * [BindingIdentifier] FunctionDeclaration
        VariableId
    % And is not assigned in the function
    where not
        FunctionDeclaration [assigns VariableId] [updates VariableId]
    % And is declared in the scope
    import Scope [SourceElement*]
    skipping [ImportDeclaration]
    deconstruct * [BindingIdentifier] Scope
        VariableId
end function

function isLiteralConstId
    match [Identifier]
        Id [id]
    construct UpperId [id]
        Id [toupper]
    deconstruct Id
        UpperId
end function

% Issue #19 : Do not add properties dynamically

rule auditDynamicProperties
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-no-obj-props-dynamic" 1]
    replace $ [SourceElement*]
        Stmt [STMT] Modifiers [AccessibilityModifier*] 'class ClassId [Identifier] TypeParameters [TypeParameters?] ClassHeritage [ClassHeritage]
            Block [BLOCK] '{ ClassBody [ClassBody?] '} End [END] Semi [SEMI]
        Scope [SourceElement*]
    by
        Stmt Modifiers 'class ClassId TypeParameters ClassHeritage Block '{ ClassBody '} End Semi
        Scope [auditEachObjectDynamicProperties ClassId ClassBody] 
end rule
        
rule auditEachObjectDynamicProperties ClassId [Identifier] ClassBody [ClassBody?]
    replace $ [SourceElement*]
        Stmt [STMT] VarLetOrConst [VarLetOrConst] ObjectId [Identifier] Nullable [Nullable?] ': ClassId Initializer [Initializer?] Semi [SEMI]
        Scope [SourceElement*]
    by
        Stmt VarLetOrConst ObjectId Nullable ': ClassId Initializer Semi
        Scope [auditEachObjectDynamicAddedProperty ObjectId ClassBody]
end rule

rule auditEachObjectDynamicAddedProperty ObjectId [Identifier] ClassBody [ClassBody?]
    replace $ [SourceElement*]
        Stmt [STMT] ExpressionType [attr ExpressionType] ObjectId '. PropertyId [Identifier] '= AssignmentExpression [AssignmentExpression] Semi [SEMI]
        MoreElements [SourceElement*]
    deconstruct not * [PropertyIdentifier] ClassBody
        PropertyId
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not add properties dynamically : hp-performance-no-obj-props-dynamic : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt ExpressionType ObjectId '. PropertyId '= AssignmentExpression Semi 'audited
        MoreElements
end rule

% Issue #28 : Avoid use of object type aliases

rule auditObjectTypeAliases
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-no-type-annotation" 1]
    replace $ [SourceElement*]
        Stmt [STMT] 'type ClassId [Identifier] '= Block [BLOCK] '{ ObjectMembers [ObjectMembers?] '} End [END] Semi [SEMI]
        MoreElements [SourceElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Avoid use of object type aliases : hp-performance-no-type-annotation : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'interface ClassId Block '{ ObjectMembers '} End Semi
        MoreElements 
end rule

% Issue #49 : Use dot notation to access object properties

rule auditObjectDotNotation
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-dot-notation-for-obj-props" 1]
    replace $ [SourceElement*]
        Stmt [STMT] Modifiers [AccessibilityModifier*] 'class ClassId [Identifier] TypeParameters [TypeParameters?] ClassHeritage [ClassHeritage]
            Block [BLOCK] '{ ClassBody [ClassBody?] '} End [END] Semi [SEMI]
        Scope [SourceElement*]
    by
        Stmt Modifiers 'class ClassId TypeParameters ClassHeritage Block '{ ClassBody '} End Semi
        Scope [auditEachObjectDotNotation ClassId] 
end rule
        
rule auditEachObjectDotNotation ClassId [Identifier]
    replace $ [SourceElement*]
        Stmt [STMT] VarLetOrConst [VarLetOrConst] ObjectId [Identifier] Nullable [Nullable?] ': ClassId Initializer [Initializer?] Semi [SEMI]
        Scope [SourceElement*]
    by
        Stmt VarLetOrConst ObjectId Nullable ': ClassId Initializer Semi
        Scope [auditEachObjectReferenceDotNotation ObjectId]
end rule

rule auditEachObjectReferenceDotNotation ObjectId [Identifier]
    replace $ [MemberExpression]
        ObjectId SubscriptOrPropertySelector [SubscriptOrPropertySelector] MoreMemberSelectors [MemberSelector*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Use dot notation to access object properties : hp-specs-dot-notation-for-obj-props : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment ObjectId SubscriptOrPropertySelector MoreMemberSelectors
end rule

% Issue #55 : Force null and undefined as independent type annotations

rule auditAssignNullUndefined
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-null-undefined-independent-types" 1]
    replace $ [SourceElement*]
        Stmt [STMT] VarLetOrConst [VarLetOrConst] VarId [Identifier] Nullable [Nullable?] ': PredefinedType [PredefinedType] Initializer [Initializer?] Semi [SEMI]
        Scope [SourceElement*]
    by
        Stmt VarLetOrConst VarId Nullable ': PredefinedType Initializer Semi
        Scope [auditAssignEachNullUndefined VarId]
end rule

rule auditAssignEachNullUndefined VarId [Identifier]
    construct NullUndefined [PrimaryExpression*]
        'null 'undefined
    replace $ [SourceElement*]
        Stmt [STMT] AssignType [attr ExpressionType] VarId '= ExpressionType [attr ExpressionType] SourceExpression [PrimaryExpression] Semi [SEMI]
        MoreElements [SourceElement*]
    deconstruct * [PrimaryExpression] NullUndefined
        SourceExpression
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not assign null or undefined to other types : hp-specs-null-undefined-independent-types : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt AssignType VarId '= ExpressionType SourceExpression Semi 'audited
        MoreElements
end rule

% Issue #40 - do not use non-numeric properties (subscripts) on arrays

rule auditNonNumericSubscripts
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-non-numeric-props-arrays" 1]
    replace $ [SourceElements]
        Scope [SourceElement*]
    by
        Scope [auditEachNonNumericSubscripts Scope]
end rule

rule auditEachNonNumericSubscripts Scope [SourceElement*]
    replace $ [MemberExpression]
        ArrayId [Identifier] '[ '[: Type [Type] '] Expression [UntypedAssignmentExpression] '] MemberSelectors [MemberSelector*]
    deconstruct not Type
        'number
    deconstruct * [SimpleVariableBinding] Scope
        ArrayId _ [Nullable?] ': _ [PrimaryType] '[ '] _ [Initializer?]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use non-numeric properties on arrays : hp-specs-no-non-numeric-props-arrays : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment ArrayId '[ '[: Type '] Expression ']
end rule

% Issue #54 - do not await non-thenables

rule auditAwaitNonThenables
    % Is this rule to be run?
    where _ [ruleEnabled "hp-specs-no-await-non-thenables" 1]
    construct NonThenableTypes [Type*]
        'number 'boolean 'string 'symbol 'void 'null 'undefined
    replace $ [AssignmentExpression]
        '[: Type [Type] '] 'await MemberExpression [MemberExpression]
    deconstruct * [Type] NonThenableTypes
        Type
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not await non-thenables : hp-specs-no-await-non-thenables : 1 : LINENUMBER : FILEPATH */
    by
        '[: Type '] AuditComment 'await MemberExpression
end rule


%%%%%%%%%%%%%%%%%%%%%%
% ArkUI auditing rules
%%%%%%%%%%%%%%%%%%%%%%

% Issue #6 : Do not use too deep nested component layout

rule auditUINestedComponents
    % Is this rule to be run?
    where _ [ruleEnabled "hp-performance-no-deep-nested-components" 1]
    % For each layout struct
    replace $ [StructDeclaration]
        Stmt [STMT] StructHeader [StructHeader] StructBody [StructBody] Semi [SEMI]
    by
        Stmt StructHeader StructBody [nestedComponent 1] Semi
end rule

rule nestedComponent N [number]
    % Walk the nested component hierarchy, counting levels
    skipping [ComponentDeclaration]
    replace $ [ComponentDeclaration]
        Stmt [STMT] ComponentHeader [ComponentHeader] ComponentBody [ComponentBody?] ComponentSelectors [ComponentSelectors?] Semi [SEMI]
    % If the level is too deep, add a warning comment
    construct CommentString [stringlit]
        _ [commentIfGreater N 4 "/* HPAudit: Do not use too deep nested component layout : hp-performance-no-deep-nested-components : 1 : LINENUMBER : FILEPATH */"]
    construct Comment [comment]
        _ [+ CommentString]
    construct AuditComment [AuditComment]
        Comment
    % On to the next level
    construct Np1 [number]
        N [+ 1]
    deconstruct ComponentBody
        Block [BLOCK] '{ ComponentElements [ComponentElement*] '} End [END]
    by
        Stmt ComponentHeader Block '{ AuditComment ComponentElements [nestedComponent Np1] '} End ComponentSelectors Semi 
end rule

function commentIfGreater N [number] Limit [number] CommentString [stringlit]
    % If we're too deep
    where 
        N [> Limit]
    % Then make a warning comment
    replace [stringlit]
        _ [stringlit]
    by
        _ [+ CommentString]
end function

% Issues 119, 130 : Limit refresh scope using containers

function auditUIIfStatements
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-limit-refresh-scope" 1]
    replace [program]
        P [program]
    by
        P [markAuditedUIIfStatements]
          [auditEachUIIfStatements]
end function

rule markAuditedUIIfStatements
    replace $ [ComponentElement]
        Stmt [STMT] 'Stack '() Block [BLOCK] '{ 
            Stmt2 [STMT] 'if '( Expression [Expression] ') Comment [CommentStatement*] ComponentBody [ComponentBody] 
                ElseIfComponents [ElseIfComponent*] ElseComponent [ElseComponent?] Send2 [SEMI]
        '} End [END] Semi [SEMI]
    by
        Stmt 'Stack '() Block '{ 
            Stmt2 'if '( Expression ') Comment ComponentBody ElseIfComponents ElseComponent Send2 'audited
        '} End Semi
end rule

rule auditEachUIIfStatements
    % Containers that can't embed Stack
    construct LimitedContainerIds [Identifier*]
        'List 'Grid 'Swiper 'WaterFlow
    replace $ [ComponentExpression]
        Decorators [DecoratorList?] ComponentId [Identifier] ComponentArguments [ComponentArguments]
            Block [BLOCK] '{ ComponentElements [ComponentElement*] '} End [END] ComponentSelectors [ComponentSelectors?]
    % Does not apply to limited containers 
    deconstruct not * [Identifier] LimitedContainerIds
        ComponentId
    % Only applies if there is more than one element in the container
    where not 
        ComponentElements [hasOnlyOneComponentElement]
    by
        Decorators ComponentId ComponentArguments 
            Block '{ ComponentElements [auditEachUIIfStatement] '} End ComponentSelectors 
end rule

function hasOnlyOneComponentElement
    match [ComponentElement*]
        ComponentElements [ComponentElement*]
    construct UncommentedComponentElements [ComponentElement*]
        ComponentElements [removeComponentElementComments]
    deconstruct UncommentedComponentElements
        _ [ComponentElement]
end function

rule removeComponentElementComments
    skipping [ComponentElement]
    replace [ComponentElement*]
        _ [CommentStatement]
        MoreElements [ComponentElement*]
    by
        MoreElements
end rule

rule auditEachUIIfStatement
    skipping [ComponentElement]
    replace $ [ComponentElement*]
        Stmt [STMT] 'if '( Expression [Expression] ') Comment [CommentStatement*] ComponentBody [ComponentBody] 
            ElseIfComponents [ElseIfComponent*] ElseComponent [ElseComponent?] Semi [SEMI]
        MoreElements [ComponentElement*]
    deconstruct * [BLOCK] ComponentBody
        Block [BLOCK]
    construct AuditComment [AuditComment]
        '/* HPAudit: Limit refresh scope using containers (fixed) : hp-arkui-limit-refresh-scope : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        'Stack () Block '{ 
            Stmt 'if '( Expression ') Comment ComponentBody ElseIfComponents ElseComponent Semi 'audited 
        '}
        MoreElements
end rule

% Issue #135 : Reduce redundant nested containers

rule auditUIRedundantComponents
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-reduce-view-nest-level" 1]
    construct SimpleComponents [Identifier*]
        'Stack 'Flex 'Row 'Column
    % Do we have an outer simple container?
    replace [ComponentElement*]
        Stmt [STMT] ComponentId [Identifier] ComponentArguments [ComponentArguments] Block [BLOCK] '{ 
            InnerComponents [ComponentElement*]
        '} End [END] OuterProperties [ComponentSelector*] Semi [SEMI]
        MoreElements [ComponentElement*]
    deconstruct * [Identifier] SimpleComponents
        ComponentId
    % With a single inner simple container?
    construct NoCommentInnerComponents [ComponentElement*]
        InnerComponents [removeComments]
    deconstruct NoCommentInnerComponents
        InnerContainer [ComponentElement]
    deconstruct InnerContainer
        Stmt2 [STMT] InnerComponentId [Identifier] InnerArguments [ComponentArguments]
            InnerComponentBody [ComponentBody?] InnerProperties [ComponentSelector*] Semi2 [SEMI]
    deconstruct * [Identifier] SimpleComponents
        InnerComponentId
    % Then we need to reduce it
    construct ReducedContainer [ComponentElement]
        Stmt2 InnerComponentId InnerArguments 
            InnerComponentBody InnerProperties [. OuterProperties] [removeRedundantComponentSelectors] Semi2
    % Preserve line numbers
    construct NewSemi [ComponentElement]
        Semi
    construct ReducedComponents [ComponentElement*]
        Stmt InnerComponents [replaceComponent InnerContainer ReducedContainer] [. NewSemi]
    construct AuditComment [AuditComment]
        '/* HPAudit: Reduce redundant nested containers (fixed) : hp-arkui-reduce-view-nest-level : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        ReducedComponents
        [. MoreElements]
end rule

rule removeComments
    skipping [ComponentElement]
    replace [ComponentElement*]
        _ [CommentStatement]
        More [ComponentElement*]
    by
        More
end rule

function replaceComponent Component1 [ComponentElement] Component2 [ComponentElement]
    replace * [ComponentElement]
        Component1
    by
        Component2
end function

% Issue #132 : Consider using LazyForEach

rule auditUIForEach
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-load-on-demand" 1]
    % Containers that can use LazyForEach
    construct LazyContainerIds [Identifier*]
        'List 'Grid 'Swiper 'WaterFlow
    replace $ [ComponentElement]
        ComponentDeclaration [ComponentDeclaration] 
    deconstruct * [ComponentIdentifier] ComponentDeclaration
        ContainerId [Identifier]
    deconstruct * [Identifier] LazyContainerIds
        ContainerId
    by
        ComponentDeclaration [auditEachUIForEach]
end rule
        
rule auditEachUIForEach 
    skipping [ComponentElement]
    replace $ [ComponentElement*]
        Stmt [STMT] 'ForEach '( MemberExpression [MemberExpression], ArrowComponentList [list ArrowComponent] ') Semi [SEMI]
        MoreElements [ComponentElement*]
    construct AuditComment [AuditComment]
        '/* HPAudit: Consider using LazyForEach for long lists : hp-arkui-load-on-demand : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'ForEach '( MemberExpression, ArrowComponentList ') Semi 'audited
        MoreElements
end rule

% Issue #127 : Use Column/Row instead of Flex

rule auditUIFlex
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-no-flex" 0]
    replace $ [ComponentElement*]
        Stmt [STMT] 'Flex ComponentArguments [ComponentArguments] ComponentBody [ComponentBody] Semi [SEMI]
        MoreElements [ComponentElement*]
    skipping [ComponentDeclaration] 
    deconstruct * [ComponentDeclaration] ComponentBody
        _ [STMT] 'Flex _ [ComponentArguments] _ [ComponentBody?] _ [ComponentSelectors?] _ [SEMI]
        construct AuditComment [AuditComment]
        '/* HPAudit: Consider using Row/Column instead of Flex : hp-arkui-no-flex : 0 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'Flex ComponentArguments ComponentBody Semi 'audited
        MoreElements
end rule

% Issue #118 : Use synchronous loading for local small images

rule auditUIAsync
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-component-async-load" 1]
    replace $ [ComponentElement*]
        Stmt [STMT] ComponentHeader [ComponentHeader] ComponentBody [ComponentBody] Semi [SEMI]
        MoreElements [ComponentElement*]
    where
        ComponentBody [containsThreeAsyncImages]
    construct AuditComment [AuditComment]
        '/* HPAudit: Consider using syncLoad for local small images (fixed) : hp-arkui-component-async-load : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt ComponentHeader ComponentBody [addSyncLoadToImages] Semi   'audited
        MoreElements
end rule

function containsThreeAsyncImages
    skipping [ComponentDeclaration]
    match * [ComponentElement*]
        _ [STMT] 'Image _ [ComponentArguments] ComponentSelectors [ComponentSelector*] _ [SEMI]
        More [ComponentElement*]
    deconstruct not * [ComponentSelector] ComponentSelectors
        '.syncLoad
    skipping [ComponentDeclaration]
    deconstruct * More
        _ [STMT] 'Image _ [ComponentArguments] MoreComponentSelectors [ComponentSelector*] _ [SEMI]
        MoreMore [ComponentElement*]
    deconstruct not * [ComponentSelector] MoreComponentSelectors
        '.syncLoad
    skipping [ComponentDeclaration]
    deconstruct * MoreMore
        _ [STMT] 'Image _ [ComponentArguments] MoreMoreComponentSelectors [ComponentSelector*] _ [SEMI]
        _ [ComponentElement*]
    deconstruct not * [ComponentSelector] MoreMoreComponentSelectors
        '.syncLoad
end function

rule addSyncLoadToImages
    skipping [ComponentDeclaration]
    replace $ [ComponentElement]
        Stmt [STMT] 'Image ComponentArguments [ComponentArguments] ComponentSelectors [ComponentSelector*] Semi [SEMI]
    construct SyncLoad [ComponentSelector*]
        '.syncLoad ( 'true ) 
    by
        Stmt 'Image ComponentArguments ComponentSelectors [. SyncLoad] Semi
end rule

% Issue #134 : Discretion of using syncLoad

rule auditUISyncLoad
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-image-async-load" 0]
    replace $ [ComponentElement*]
        Stmt [STMT] ComponentHeader [ComponentHeader] ComponentBody [ComponentBody] Semi [SEMI]
        MoreElements [ComponentElement*]
    where
        ComponentBody [containsSyncLoadImage]
    by
        Stmt ComponentHeader ComponentBody [removeSyncLoadFromImages] Semi  'audited
        MoreElements
end rule

function containsSyncLoadImage
    skipping [ComponentDeclaration]
    % Find some Image
    match * [ComponentElement]
        _ [STMT] 'Image _ [ComponentArguments] ComponentSelectors [ComponentSelector*] _ [SEMI]
    % That has a syncLoad
    deconstruct * ComponentSelectors
         '.syncLoad ( _ [CLIST] _ [attr ExpressionType] 'true _ [COMMA] ) _ [ComponentSelector*]
end function

rule removeSyncLoadFromImages
    skipping [ComponentDeclaration]
    replace $ [ComponentElement]
        Stmt [STMT] 'Image ComponentArguments [ComponentArguments] ComponentSelectors [ComponentSelector*] Semi [SEMI]
    by
        Stmt 'Image ComponentArguments ComponentSelectors [removeSyncLoad] Semi
end rule

function removeSyncLoad
    replace * [ComponentSelector*]
        '.syncLoad ( _ [CLIST] _ [attr ExpressionType] 'true Comma [COMMA] ) 
        ComponentSelectors [ComponentSelector*]
    deconstruct * [srclinenumber] Comma
        SrcLineNumber [srclinenumber]
    construct Comment [comment]
        _ [+ "/* HPAudit: Consider using asynchronous load for large images (fixed) : hp-arkui-image-async-load : 0 : "] [+ SrcLineNumber] [+ " : FILEPATH */"]
    construct AuditComment [AuditComment]
        Comment
    by
        AuditComment
        ComponentSelectors
end function

% Issue #121 : Remove redundant Stack blocks

rule auditUIRedundantStack
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-no-redundant-nest" 1]
    replace [ComponentElement*]
        Stmt [STMT] 'Stack ComponentArguments [ComponentArguments] Block [BLOCK] '{ 
            Stmt2 [STMT] InnerIdentifier [Identifier] InnerArguments [ComponentArguments]
                InnerComponentBody [ComponentBody?] InnerComponentSelectors [ComponentSelector*] Send2 [SEMI]
        '} End [END] ComponentSelectors [ComponentSelector*] _ [SEMI]
        More [ComponentElement*]
    construct AuditCommentString [comment]
        '/* HPAudit: Remove redundant Stack containers (fixed) : hp-arkui-no-redundant-nest : 1 : LINENUMBER : FILEPATH */
    construct AuditComment [AuditComment]
        AuditCommentString
    % Preserve source line number 
    deconstruct * [srclinenumber] Block
        SrcLineNumber [srclinenumber]
    by
        AuditComment
        SrcLineNumber
        Stmt InnerIdentifier InnerArguments 
            InnerComponentBody InnerComponentSelectors [. ComponentSelectors] [removeRedundantComponentSelectors] Send2
        More
end rule

rule removeRedundantComponentSelectors
    replace [ComponentSelector*]
        '. ComponentSelectorId [Identifier] ComponentArguments [ComponentArguments]  MoreComponentSelectors [ComponentSelector*]
    deconstruct * [ComponentSelector] MoreComponentSelectors
        '. ComponentSelectorId
    by
        '. ComponentSelectorId ComponentArguments MoreComponentSelectors [removeComponentSelector ComponentSelectorId]
end rule

rule removeComponentSelector ComponentSelectorId [Identifier]
    replace [ComponentSelector*]
        '. ComponentSelectorId _ [ComponentArguments] MoreComponentSelectors [ComponentSelector*]
    by
        MoreComponentSelectors
end rule

% Issue #201 : Do not use log operations in high frequency component properties in release version
% Issue #187 : No hilog() in onscroll() properties
% Issue #197 : No log operation in onTouch call in release version 
% Issue #198 : No log operation in onActionUpdate call in release version

rule auditUIHighFreqLogOperations
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-no-high-freq-log" 1]
    % High frequency actions
    export HighFrequencyActionIds [Identifier*]
        % hp-arkui-high-freq-call-hilog
        'onScroll
        % hp-arkui-log-ontouch
        'onTouch
        % hp-arkui-log-onactionupdate
        'onActionUpdate
        % hp-arkui-no-high-freq-log
        'onItemDragMove
        'onDragMove
        'onMouse
    % Log operations
    export LogOperations [MemberExpression*]
        'hilog '. 'info
        'hilog '. 'warn
        'hilog '. 'debug
        'console '. 'info
        'console '. 'log 
    replace $ [StructBody]
        StructBody [StructBody]
    by
        StructBody [auditUIEachHighFreqLogOperations StructBody]
end rule

rule auditUIEachHighFreqLogOperations StructBody [StructBody]
    import HighFrequencyActionIds [Identifier*]
    replace $ [ComponentSelector*]
        '. ActionId [Identifier] ComponentArguments [ComponentArguments] MoreComponentSelectors [ComponentSelector*]
    deconstruct * [Identifier] HighFrequencyActionIds
        ActionId
    by
        '. ActionId ComponentArguments [auditEachDirectHighFreqLogOperation] [auditEachIndirectHighFreqLogOperation StructBody] 
           MoreComponentSelectors 
end rule

rule auditEachDirectHighFreqLogOperation
    replace $ [SourceElement*]
        Stmt [STMT] LogOperation [MemberExpression] Semi [SEMI]
        MoreElements [SourceElement*]
    where 
        LogOperation [isLogOperation]
    construct AuditComment [AuditComment]
        % hp-arkui-high-freq-call-hilog + hp-arkui-log-ontouch + hp-arkui-log-onactionupdate = hp-arkui-no-high-freq-log
        '/* HPAudit: Do not use high frequency log operations in release version (fixed) : hp-arkui-no-high-freq-log : 1 : LINENUMBER : FILEPATH */
    construct LogOperationComment [comment]
        _ [+ "// "] [unparse LogOperation]
    % Preserve source line number 
    deconstruct * [srclinenumber] Stmt
        SrcLineNumber [srclinenumber]
    by
        AuditComment
        SrcLineNumber
        LogOperationComment
        MoreElements
end rule

function isLogOperation
    import LogOperations [MemberExpression*]
    match [MemberExpression] 
        Log [Identifier] '. Operation [Identifier] _ [Arguments]
    deconstruct * [MemberExpression] LogOperations
        Log '. Operation
end function

rule auditEachIndirectHighFreqLogOperation StructBody [StructBody]
    replace $ [SourceElement*]
        Stmt [STMT] 'this '. FunctionId [Identifier] Arguments [Arguments] Semi [SEMI]
        MoreElements [SourceElement*]
    deconstruct * [MemberFunctionDeclaration] StructBody
        _ [STMT] _ [AccessibilityModifier*] _ ['async ?] FunctionId _ [Nullable ?] _ [CallSignature] FunctionBody [OptionalFunctionBody] _ [SEMI]
    where
        FunctionBody [containsLogOperation]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use high frequency log operations in release version : hp-arkui-no-high-freq-log : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'this '. FunctionId Arguments Semi 'audited
        MoreElements
end rule

function containsLogOperation
    import LogOperations [MemberExpression*]
    match * [MemberExpression] 
        Log [Identifier] '. Operation [Identifier] _ [Arguments]
    deconstruct * [MemberExpression] LogOperations
        Log '. Operation
end function

% Issue #201 : Do not use log operations in high frequency component properties in release version
% Issue #188 : No hilog() in component property function calls

rule auditUIFunctionHighFreqLogOperations
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-no-high-freq-log" 1]
    replace $ [StructElement*]
        Stmt [STMT] Modifiers [AccessibilityModifier*] Async ['async ?] FunctionId [Identifier] Nullable [Nullable ?] CallSignature [CallSignature] 
            FunctionBody [OptionalFunctionBody] Semi [SEMI]
        MoreElements [StructElement*]
    where 
        FunctionBody [containsLogOperation]
    where
        MoreElements [hasComponentPropertyCall FunctionId]
    by
        Stmt Modifiers Async FunctionId Nullable CallSignature FunctionBody [auditEachHighFreqLogOperation] Semi
        MoreElements
end rule

function hasComponentPropertyCall FunctionId [Identifier]
    match * [ComponentSelector*]
        '. PropertyId [Identifier] ComponentArguments [ComponentArguments] _ [ComponentSelector*]
    deconstruct * [MemberExpression] ComponentArguments
        'this '. FunctionId _ [Arguments]
end function

rule auditEachHighFreqLogOperation
    replace $ [SourceElement*]
        Stmt [STMT] LogOperation [MemberExpression] Semi [SEMI]
        MoreElements [SourceElement*]
    where
        LogOperation [isLogOperation]
    construct AuditComment [AuditComment]
        '/* HPAudit: Do not use high frequency log operations in release version (fixed) : hp-arkui-no-high-freq-log : 1 : LINENUMBER : FILEPATH */
    construct LogOperationComment [comment]
        _ [+ "// "] [unparse LogOperation]
    % Preserve source line number 
    deconstruct * [srclinenumber] Semi
        SrcLineNumber [srclinenumber]
    by
        AuditComment
        SrcLineNumber
        LogOperationComment
        MoreElements
end rule

% Issue #122 : Optimize nesting levels using flat layout

rule auditUINestLevels
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-optimize-nest-levels" 1]
    replace $ [ComponentElement*]
        Stmt [STMT] 'Flex ComponentArguments [ComponentArguments] Block [BLOCK] '{ 
            InnerFlexElement [ComponentElement]
            InnerMoreElements [ComponentElement*]
        '} End [END] ComponentSelectors [ComponentSelector*] Semi [SEMI]
        MoreElements [ComponentElement*]
    deconstruct InnerFlexElement
        _ [STMT] 'Flex _ [ComponentArguments] _ [BLOCK] '{ 
            ComponentElements [ComponentElement*]
        '} _ [END] _ [ComponentSelector*] _ [SEMI]
    construct AuditComment [AuditComment]
        '/* HPAudit: Consider using flat RelativeLayout in place of nested Flex : hp-arkui-optimize-nest-levels : 1 : LINENUMBER : FILEPATH */
    by
        AuditComment
        Stmt 'Flex ComponentArguments Block '{ 
            InnerFlexElement
            InnerMoreElements
        '} End ComponentSelectors Semi 'audited
        MoreElements
end rule

% Issue #125 : Set width and height of lazy list components

rule auditUIListWidthHeight
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-set-list-width-height" 1]
    replace $ [ComponentElement*]
        Stmt [STMT] 'Scroll ComponentArguments [ComponentArguments] Block [BLOCK] '{ 
            ListElement [ComponentElement]
        '} End [END] ComponentSelectors [ComponentSelector*] Semi [SEMI]
        MoreElements [ComponentElement*]
    deconstruct ListElement
        _ [STMT] 'List _ [ComponentArguments] _ [BLOCK] '{ 
            LazyForEachElement [ComponentElement]
        '} _ [END] ListComponentSelectors [ComponentSelector*] _ [SEMI]
    deconstruct LazyForEachElement
        _ [STMT] 'LazyForEach '( _ [MemberExpression], _ [list ArrowComponent+] ') _ [SEMI]
    where not all
        ListComponentSelectors [hasProperty 'width] [hasProperty 'height]
    construct AuditComment [AuditComment]
        '/* HPAudit: Set width and height of lazy List : hp-arkui-set-list-width-height : 1 : LINENUMBER : FILEPATH */
    by
        Stmt
        'Scroll ComponentArguments Block '{ 
            AuditComment
            ListElement
        '} End ComponentSelectors 
        Semi
        MoreElements
end rule

function hasProperty PropertyId [id]
    match * [ComponentSelector]
        '. PropertyId
end function

% Issue #143 : Merge AnimateTo same params

rule auditUIAnimateToSameParams
    % Is this rule to be run?
    where _ [ruleEnabled "hp-arkui-same-animateto-same-params" 1]
    replace $ [StructDeclaration]
        StructDeclaration [StructDeclaration]
    export AnimateTos [MemberExpression*]
        _ % none
    where
        StructDeclaration [getAnimateTos] 
    import AnimateTos
    % at least two
    deconstruct AnimateTos
        _ [MemberExpression]
        _ [MemberExpression]
        _ [MemberExpression*]
    construct SameAnimateTos [MemberExpression*]
        AnimateTos [sameAnimateToParamsOnly]
    by
        StructDeclaration [auditEachAnimateToSameParams each AnimateTos]
end rule

rule getAnimateTos
    match $ [MemberExpression]
        'animateTo Arguments [Arguments]
    import AnimateTos [MemberExpression*]
    export AnimateTos
        'animateTo Arguments
        AnimateTos
end rule

rule sameAnimateToParamsOnly
    replace [MemberExpression*]
        'animateTo '( FirstArgument [AssignmentExpression] ', SecondArgument [AssignmentExpression] ')
        MoreAnimateTos [MemberExpression*]
    deconstruct not * [MemberExpression] MoreAnimateTos
        'animateTo '( FirstArgument ', _ [AssignmentExpression] ')
    by
        MoreAnimteTos
end rule

function auditEachAnimateToSameParams AnimateTo [MemberExpression]
    replace * [MemberExpression]
        AnimateTo
    construct AuditComment [AuditComment]
        '/* HPAudit: Consider merging animateTo with same parameters : hp-arkui-same-animateto-same-params : 1 : LINENUMBER : FILEPATH */
    deconstruct AnimateTo
        'animateTo Arguments [Arguments]
    by
        AuditComment 'animateTo Arguments
end function


%%%%%%%%%%%%%%%
% Utility rules
%%%%%%%%%%%%%%%

% Clear audited attributes
rule clearAttributes
    replace $ [attr 'audited]
        'audited
    by
        % none
end rule

% Fix TXL leading NL comment bug
function fixLeadingNLComment
    replace [program]
        Comment [CommentStatement]
        MoreElements [SourceElement*]
    by
        Comment [stripLeadingNL]
        MoreElements
end function

function stripLeadingNL
    replace * [comment]
        Comment [comment]
    construct CommentString [stringlit]
        _ [+ Comment]
    where not
        CommentString [egrep "^/"]
    by
        Comment [: 2 999]
end function
